styimat 1.8.0 → 1.11.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 +67 -19
- package/convert-styimat.js +155 -60
- package/package.json +2 -1
- package/styimat.js +293 -46
- package/styimat.min.js +115 -16
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Styimat
|
|
2
2
|
[](https://gitee.com/wxy6987/styimat/stargazers)
|
|
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
|
|
|
10
10
|
## 安装方式
|
|
11
11
|
|
|
@@ -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
|
|
@@ -43,9 +43,9 @@ npm install styimat
|
|
|
43
43
|
- **Lab/LCH 颜色支持** - 将 Lab 和 LCH 颜色转换为 CSS 兼容的 RGB
|
|
44
44
|
- **广色域显示** - 自动检测并支持 Display P3 广色域显示器
|
|
45
45
|
- **变量系统** - 类似 Sass 的变量系统,支持作用域变量
|
|
46
|
-
|
|
47
|
-
- **十六进制语法** - 支持 `lab#
|
|
48
|
-
- **增强数学计算** - 支持 `math()
|
|
46
|
+
- **嵌套规则** - 支持 Sass 风格的嵌套选择器
|
|
47
|
+
- **十六进制语法** - 支持 `lab#LLAABB` 和 `lch#LLCCHHH` 简洁语法
|
|
48
|
+
- **增强数学计算** - 支持 `math()`` 函数和复杂数学表达式
|
|
49
49
|
- **轻量高效** - 无依赖,压缩后仅约 20KB
|
|
50
50
|
- **多种使用方式** - 浏览器、Node.js、命令行均可使用
|
|
51
51
|
- **灵活API** - 支持函数调用、模板标签、对象配置等多种用法
|
|
@@ -124,6 +124,10 @@ npx styimat
|
|
|
124
124
|
# 全局安装后使用
|
|
125
125
|
npm install -g styimat
|
|
126
126
|
styimat input.css output.css
|
|
127
|
+
|
|
128
|
+
# 监听
|
|
129
|
+
npx styimat --watch input.css output.css
|
|
130
|
+
npx styimat -w input.css output.css
|
|
127
131
|
```
|
|
128
132
|
|
|
129
133
|
## 高级用法
|
|
@@ -226,7 +230,6 @@ ele.cssVar[prop] = value; // 设置prop变量为value
|
|
|
226
230
|
ele.cssVar(prop); // 获取prop变量的值
|
|
227
231
|
ele.cssVar.prop; // 获取prop变量的值
|
|
228
232
|
ele.cssVar[prop] // 获取prop变量的值
|
|
229
|
-
|
|
230
233
|
```
|
|
231
234
|
|
|
232
235
|
### 4. 混合使用示例
|
|
@@ -277,9 +280,37 @@ $font-family: 'Inter', sans-serif;
|
|
|
277
280
|
}
|
|
278
281
|
```
|
|
279
282
|
|
|
283
|
+
### 导入语法
|
|
284
|
+
|
|
285
|
+
#### 您可以在CSS文件的开头使用导入文件来导入其他css文件,应该以`@import开头`:
|
|
286
|
+
|
|
287
|
+
```css
|
|
288
|
+
@import "head.css";
|
|
289
|
+
@import url("tail.css");
|
|
290
|
+
|
|
291
|
+
/* 然后写您的CSS代码 */
|
|
292
|
+
$primary-color: lab#32a852;
|
|
293
|
+
$spacing: math(16px * 2 + 4px);
|
|
294
|
+
|
|
295
|
+
body {
|
|
296
|
+
color: $primary-color;
|
|
297
|
+
padding: $spacing;
|
|
298
|
+
|
|
299
|
+
.container {
|
|
300
|
+
margin: math($spacing / 2);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### 还可以使用style语法:
|
|
306
|
+
```html
|
|
307
|
+
<!--这样就自动处理好了-->
|
|
308
|
+
<style src="head.css" e></style>
|
|
309
|
+
```
|
|
310
|
+
|
|
280
311
|
### 配置头语法
|
|
281
312
|
|
|
282
|
-
您可以在CSS文件的开头使用配置头来设置预处理选项,配置行以
|
|
313
|
+
您可以在CSS文件的开头使用配置头来设置预处理选项,配置行以 `#`` 开头:
|
|
283
314
|
|
|
284
315
|
```css
|
|
285
316
|
#indentSize 4
|
|
@@ -305,6 +336,8 @@ body {
|
|
|
305
336
|
}
|
|
306
337
|
```
|
|
307
338
|
|
|
339
|
+
**注意**:配置头既支持驼峰命名也支持连字符命名,例如 `indent-size` 和 `indentSize` 效果相同。
|
|
340
|
+
|
|
308
341
|
### Math 计算语法
|
|
309
342
|
|
|
310
343
|
```css
|
|
@@ -356,7 +389,7 @@ body {
|
|
|
356
389
|
## 十六进制颜色语法详解
|
|
357
390
|
|
|
358
391
|
### Lab 十六进制格式:`lab#LLAABB`
|
|
359
|
-
Lab 颜色使用 `lab
|
|
392
|
+
Lab 颜色使用 `lab#`` 前缀,后跟 6 个十六进制字符:
|
|
360
393
|
- 前 2 位:L(明度),范围 00-FF,映射到 0-100
|
|
361
394
|
- 中间 2 位:A(红绿轴),范围 00-FF,映射到 -192 到 192
|
|
362
395
|
- 后 2 位:B(黄蓝轴),范围 00-FF,映射到 -192 到 192
|
|
@@ -367,7 +400,7 @@ Lab 颜色使用 `lab#` 前缀,后跟 6 个十六进制字符:
|
|
|
367
400
|
- B = 0x80 = 128 → (128-128) × 1.5 = 0
|
|
368
401
|
|
|
369
402
|
### LCH 十六进制格式:`lch#LLCCHHH`
|
|
370
|
-
LCH 颜色使用 `lch
|
|
403
|
+
LCH 颜色使用 `lch#`` 前缀,后跟 6 个字符(最后1-3位是十进制色相值):
|
|
371
404
|
- 前 2 位:L(明度),范围 00-FF,映射到 0-100
|
|
372
405
|
- 中间 2 位:C(色度),范围 00-FF,映射到 0-150
|
|
373
406
|
- 后 1-3 位:H(色相),范围 0-100,映射到 0-360度
|
|
@@ -443,7 +476,13 @@ styimat.config({
|
|
|
443
476
|
enableMath: true,
|
|
444
477
|
mathPrecision: 6,
|
|
445
478
|
indentSize: 2,
|
|
446
|
-
enableNesting: true
|
|
479
|
+
enableNesting: true,
|
|
480
|
+
importBaseUrl: '', // 导入基础URL
|
|
481
|
+
importCache: true, // 缓存导入文件
|
|
482
|
+
importTimeout: 5000, // 导入超时时间
|
|
483
|
+
autoProcessStyleTags: true,
|
|
484
|
+
styleTagAttribute: 'e',
|
|
485
|
+
preserveOriginal: false
|
|
447
486
|
});
|
|
448
487
|
|
|
449
488
|
// 数学工具
|
|
@@ -457,24 +496,33 @@ const lab = styimat.colorUtils.lchToLab(54.7, 78.9, 38.5);
|
|
|
457
496
|
|
|
458
497
|
// 解析颜色
|
|
459
498
|
const colorInfo = styimat.colorUtils.parseColor('lab(54.7 77.9 80.1 / 0.8)');
|
|
499
|
+
|
|
500
|
+
// 导入工具
|
|
501
|
+
styimat.imports.clearCache(); // 清除导入缓存
|
|
502
|
+
styimat.imports.setBaseUrl('/css/'); // 设置导入基础URL
|
|
503
|
+
styimat.imports.setCacheEnabled(false); // 禁用导入缓存
|
|
504
|
+
styimat.imports.setTimeout(10000); // 设置导入超时时间
|
|
460
505
|
```
|
|
461
506
|
|
|
462
507
|
### 配置选项
|
|
463
508
|
|
|
464
509
|
| 选项 | 类型 | 默认值 | 描述 |
|
|
465
510
|
|------|------|--------|------|
|
|
466
|
-
| `rootSelector` | string |
|
|
467
|
-
| `variablePrefix` | string |
|
|
468
|
-
| `preserveOriginal` | boolean | `false` | 是否保留原始 `<style e
|
|
469
|
-
| `indentSize` | number | `
|
|
511
|
+
| `rootSelector` | string | `:root` | CSS 变量定义的根选择器 |
|
|
512
|
+
| `variablePrefix` | string | `--`` | CSS 变量前缀 |
|
|
513
|
+
| `preserveOriginal` | boolean | `false` | 是否保留原始 `<style e>`` 标签 |
|
|
514
|
+
| `indentSize` | number | `4` | 嵌套规则的缩进大小 |
|
|
470
515
|
| `enableNesting` | boolean | `true` | 是否启用嵌套规则 |
|
|
471
516
|
| `autoProcessStyleTags` | boolean | `true` | 是否自动处理页面中的 style 标签 |
|
|
472
|
-
| `styleTagAttribute` | string | `
|
|
517
|
+
| `styleTagAttribute` | string | `e` | 标识需要处理的 style 标签属性 |
|
|
473
518
|
| `convertLabToRGB` | boolean | `true` | 是否转换 Lab 颜色为 RGB |
|
|
474
519
|
| `convertLchToRGB` | boolean | `true` | 是否转换 LCH 颜色为 RGB |
|
|
475
520
|
| `enableP3` | boolean | `true` | 是否启用 Display P3 广色域支持 |
|
|
476
|
-
| `enableMath` | boolean | `true` | 是否启用 `math()
|
|
521
|
+
| `enableMath` | boolean | `true` | 是否启用 `math()`` 函数增强 |
|
|
477
522
|
| `mathPrecision` | number | `6` | 数学计算的精度 |
|
|
523
|
+
| `importBaseUrl` | string | `` | 导入的基础URL |
|
|
524
|
+
| `importCache` | boolean | `true` | 是否缓存导入的文件 |
|
|
525
|
+
| `importTimeout` | number | `5000` | 导入超时时间(毫秒) |
|
|
478
526
|
|
|
479
527
|
## 示例
|
|
480
528
|
|
|
@@ -584,7 +632,7 @@ $primary-transparent: lab(55 190 0 / 0.8);
|
|
|
584
632
|
欢迎贡献代码!请遵循以下步骤:
|
|
585
633
|
|
|
586
634
|
1. **Fork 本仓库**
|
|
587
|
-
[](https://gitee.com/wxy6987/styimat/members)
|
|
588
636
|
2. **创建特性分支**
|
|
589
637
|
```bash
|
|
590
638
|
git checkout -b feature/AmazingFeature
|
package/convert-styimat.js
CHANGED
|
@@ -1,89 +1,184 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// 1. 只做一件事,做好
|
|
4
|
-
// 2. 输入 stdin,输出 stdout
|
|
5
|
-
// 3. 错误输出到 stderr
|
|
6
|
-
// 4. 安静模式可用
|
|
7
|
-
// 5. 返回正确的退出码
|
|
8
|
-
|
|
9
3
|
const fs = require('fs');
|
|
10
4
|
const path = require('path');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
11
6
|
|
|
12
|
-
function main() {
|
|
7
|
+
async function main() {
|
|
13
8
|
const args = process.argv.slice(2);
|
|
14
9
|
|
|
10
|
+
// 解析参数
|
|
15
11
|
let inputContent = '';
|
|
16
12
|
let outputFile = null;
|
|
13
|
+
let watchMode = false;
|
|
14
|
+
let inputFile = null;
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
const arg = args[i];
|
|
18
|
+
|
|
19
|
+
if (arg === '--watch' || arg === '-w') {
|
|
20
|
+
watchMode = true;
|
|
21
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
22
|
+
showHelp();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
} else if (arg.startsWith('-')) {
|
|
25
|
+
console.error(`错误: 未知选项 ${arg}`);
|
|
26
|
+
showHelp();
|
|
27
|
+
process.exit(1);
|
|
28
|
+
} else if (!inputFile) {
|
|
29
|
+
inputFile = arg;
|
|
30
|
+
} else if (!outputFile) {
|
|
31
|
+
outputFile = arg;
|
|
32
|
+
} else {
|
|
33
|
+
console.error('错误: 参数过多');
|
|
34
|
+
showHelp();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
function showHelp() {
|
|
40
|
+
console.log(`
|
|
41
|
+
用法: huecss-converter [选项] [输入文件] [输出文件]
|
|
42
|
+
|
|
43
|
+
选项:
|
|
44
|
+
-w, --watch 监控输入文件变化并自动转换
|
|
45
|
+
-h, --help 显示帮助信息
|
|
46
|
+
|
|
47
|
+
示例:
|
|
48
|
+
huecss-converter # 从 stdin 读取,输出到 stdout
|
|
49
|
+
huecss-converter input.css # 从文件读取,输出到 stdout
|
|
50
|
+
huecss-converter input.css output.css # 从文件读取,输出到文件
|
|
51
|
+
huecss-converter -w input.css output.css # 监控并自动转换
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 如果没有输入文件,从 stdin 读取
|
|
56
|
+
if (!inputFile) {
|
|
57
|
+
if (watchMode) {
|
|
58
|
+
console.error('错误: 监控模式需要指定输入文件');
|
|
59
|
+
process.exit(1);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
inputContent = fs.readFileSync(0, 'utf8');
|
|
63
|
+
await convertAndOutput(inputContent, null);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 检查输入文件是否存在
|
|
68
|
+
if (!fs.existsSync(inputFile)) {
|
|
69
|
+
console.error(`错误: 输入文件 "${inputFile}" 不存在`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 如果是监控模式
|
|
74
|
+
if (watchMode) {
|
|
75
|
+
if (!outputFile) {
|
|
76
|
+
console.error('错误: 监控模式需要指定输出文件');
|
|
77
|
+
process.exit(1);
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
console.log(`监控模式已启动: ${inputFile} -> ${outputFile}`);
|
|
81
|
+
console.log('按 Ctrl+C 退出');
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
// 初始转换
|
|
84
|
+
await convertFile(inputFile, outputFile);
|
|
85
|
+
|
|
86
|
+
// 监控文件变化
|
|
87
|
+
const watcher = chokidar.watch(inputFile, {
|
|
88
|
+
persistent: true,
|
|
89
|
+
ignoreInitial: true,
|
|
90
|
+
awaitWriteFinish: {
|
|
91
|
+
stabilityThreshold: 200,
|
|
92
|
+
pollInterval: 100
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
watcher
|
|
97
|
+
.on('change', async (filePath) => {
|
|
98
|
+
console.log(`${new Date().toLocaleTimeString()} - 检测到变化: ${filePath}`);
|
|
99
|
+
await convertFile(filePath, outputFile);
|
|
100
|
+
})
|
|
101
|
+
.on('error', (error) => {
|
|
102
|
+
console.error(`监控错误: ${error.message}`);
|
|
103
|
+
});
|
|
71
104
|
|
|
72
|
-
//
|
|
73
|
-
|
|
105
|
+
// 处理进程终止
|
|
106
|
+
process.on('SIGINT', () => {
|
|
107
|
+
console.log('\n停止监控...');
|
|
108
|
+
watcher.close();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
} else {
|
|
113
|
+
// 非监控模式
|
|
114
|
+
inputContent = fs.readFileSync(inputFile, 'utf8');
|
|
115
|
+
await convertAndOutput(inputContent, outputFile);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function convertFile(inputFilePath, outputFilePath) {
|
|
120
|
+
try {
|
|
121
|
+
const inputContent = fs.readFileSync(inputFilePath, 'utf8');
|
|
122
|
+
const outputContent = await convertHuecss(inputContent);
|
|
123
|
+
fs.writeFileSync(outputFilePath, outputContent, 'utf8');
|
|
124
|
+
console.log(`${new Date().toLocaleTimeString()} - 转换完成: ${inputFilePath} -> ${outputFilePath}`);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(`${new Date().toLocaleTimeString()} - 转换错误: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function convertAndOutput(inputContent, outputFile) {
|
|
131
|
+
try {
|
|
132
|
+
const outputContent = await convertHuecss(inputContent);
|
|
74
133
|
|
|
75
|
-
// 输出结果
|
|
76
134
|
if (outputFile) {
|
|
77
135
|
fs.writeFileSync(outputFile, outputContent, 'utf8');
|
|
78
136
|
} else {
|
|
79
137
|
process.stdout.write(outputContent + '\n');
|
|
80
138
|
}
|
|
81
|
-
|
|
82
139
|
} catch (error) {
|
|
83
|
-
console.error('
|
|
140
|
+
console.error('转换错误:', error.message);
|
|
84
141
|
process.exit(1);
|
|
85
142
|
}
|
|
86
143
|
}
|
|
87
144
|
|
|
88
|
-
|
|
89
|
-
|
|
145
|
+
async function convertHuecss(inputContent) {
|
|
146
|
+
// 尝试从多个位置加载 styimat.js
|
|
147
|
+
const possiblePaths = [
|
|
148
|
+
path.join(__dirname, 'styimat.js'),
|
|
149
|
+
path.join(process.cwd(), 'styimat.js'),
|
|
150
|
+
path.join(process.cwd(), 'node_modules', 'styimat', 'styimat.js')
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
let huecssModule = null;
|
|
154
|
+
for (const modulePath of possiblePaths) {
|
|
155
|
+
try {
|
|
156
|
+
if (fs.existsSync(modulePath)) {
|
|
157
|
+
huecssModule = require(modulePath);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// 继续尝试下一个路径
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!huecssModule) {
|
|
166
|
+
throw new Error('找不到 styimat.js 模块');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 获取转换函数
|
|
170
|
+
const huecss = huecssModule.huecss || huecssModule;
|
|
171
|
+
|
|
172
|
+
if (typeof huecss.convert !== 'function') {
|
|
173
|
+
throw new Error('styimat.convert 不是函数');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 执行转换
|
|
177
|
+
return await huecss.convert(inputContent);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 处理 promise 错误
|
|
181
|
+
main().catch(error => {
|
|
182
|
+
console.error('程序错误:', error.message);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "styimat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "一个高效的CSS变量预处理库,支持Lab/LCH颜色空间自动转换、嵌套选择器和Display P3广色域,让现代CSS开发更简洁强大。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"git:auto-tag": "git tag -d v$(node -p \"require('./package.json').version\") 2>/dev/null || true && git tag -a v$(node -p \"require('./package.json').version\") -m 'Version $(node -p \"require('./package.json').version\")' && git push && git push --tags"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"chokidar": "^5.0.0",
|
|
56
57
|
"terser": "^5.44.1"
|
|
57
58
|
}
|
|
58
59
|
}
|
package/styimat.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* 支持 Display P3 广色域显示器
|
|
11
11
|
* 增强 math() 函数,支持复杂数学计算
|
|
12
12
|
* 支持配置头
|
|
13
|
+
* 支持 @import 语法
|
|
13
14
|
*/
|
|
14
15
|
(function(root, factory) {
|
|
15
16
|
if (typeof define === 'function' && define.amd) {
|
|
@@ -37,10 +38,16 @@
|
|
|
37
38
|
enableP3: true,
|
|
38
39
|
enableMath: true, // 启用math()函数增强
|
|
39
40
|
mathPrecision: 6, // 数学计算精度
|
|
41
|
+
importBaseUrl: '', // @import 的基础URL
|
|
42
|
+
importCache: true, // 缓存导入的文件
|
|
43
|
+
importTimeout: 5000, // 导入超时时间(毫秒)
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
// 全局P3支持检测结果
|
|
43
47
|
let p3Supported = null;
|
|
48
|
+
|
|
49
|
+
// 导入文件缓存
|
|
50
|
+
const importCache = new Map();
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
53
|
* 解析配置头
|
|
@@ -1209,7 +1216,7 @@
|
|
|
1209
1216
|
replaceVariableUsesInValue(value, variables),
|
|
1210
1217
|
config
|
|
1211
1218
|
);
|
|
1212
|
-
return
|
|
1219
|
+
return " ".repeat(config.indentSize) + `--${name}: ${processedValue};`;
|
|
1213
1220
|
})
|
|
1214
1221
|
.join('\n');
|
|
1215
1222
|
|
|
@@ -1248,40 +1255,201 @@
|
|
|
1248
1255
|
return result.trim();
|
|
1249
1256
|
}
|
|
1250
1257
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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;
|
|
1327
|
+
|
|
1328
|
+
let content;
|
|
1256
1329
|
|
|
1257
|
-
//
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1330
|
+
// 检查是否为Node.js环境
|
|
1331
|
+
const isNode = typeof process !== 'undefined' &&
|
|
1332
|
+
process.versions &&
|
|
1333
|
+
process.versions.node;
|
|
1260
1334
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
if (config.enableNesting && cssWithoutVars.includes('{')) {
|
|
1335
|
+
if (isNode && typeof require !== 'undefined') {
|
|
1336
|
+
// Node.js环境:使用fs模块
|
|
1264
1337
|
try {
|
|
1265
|
-
|
|
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
|
|
1353
|
+
try {
|
|
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();
|
|
1266
1371
|
} catch (error) {
|
|
1267
|
-
|
|
1372
|
+
if (error.name === 'AbortError') {
|
|
1373
|
+
throw new Error(`导入超时: ${fullUrl}`);
|
|
1374
|
+
}
|
|
1375
|
+
throw new Error(`无法获取CSS: ${fullUrl} - ${error.message}`);
|
|
1268
1376
|
}
|
|
1269
1377
|
}
|
|
1270
1378
|
|
|
1271
|
-
//
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
// 4. 注入选择器局部变量
|
|
1275
|
-
let finalCSS = processedCSS;
|
|
1276
|
-
if (selectorVariables.size > 0) {
|
|
1277
|
-
finalCSS = injectSelectorVariables(processedCSS, selectorVariables, config);
|
|
1379
|
+
// 缓存内容
|
|
1380
|
+
if (config.importCache) {
|
|
1381
|
+
importCache.set(url, content);
|
|
1278
1382
|
}
|
|
1279
1383
|
|
|
1280
|
-
|
|
1281
|
-
|
|
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 };
|
|
1282
1433
|
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
+
}
|
|
1285
1453
|
}
|
|
1286
1454
|
|
|
1287
1455
|
// 自动处理带有 e 属性的 style 标签
|
|
@@ -1290,22 +1458,35 @@
|
|
|
1290
1458
|
const styleTags = document.querySelectorAll(`style[${config.styleTagAttribute || 'e'}]`);
|
|
1291
1459
|
|
|
1292
1460
|
styleTags.forEach(styleTag => {
|
|
1293
|
-
|
|
1294
|
-
const convertedCSS = convert(originalCSS, config);
|
|
1461
|
+
let originalCSS = styleTag.textContent;
|
|
1295
1462
|
|
|
1296
|
-
//
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
+
};
|
|
1302
1488
|
|
|
1303
|
-
|
|
1304
|
-
if (!config.preserveOriginal) {
|
|
1305
|
-
styleTag.remove();
|
|
1306
|
-
} else {
|
|
1307
|
-
styleTag.style.display = 'none';
|
|
1308
|
-
}
|
|
1489
|
+
processStyleTag();
|
|
1309
1490
|
});
|
|
1310
1491
|
|
|
1311
1492
|
return styleTags.length;
|
|
@@ -1320,11 +1501,31 @@
|
|
|
1320
1501
|
const config = { ...defaultConfig, ...customConfig };
|
|
1321
1502
|
|
|
1322
1503
|
if (cssText) {
|
|
1323
|
-
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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
|
+
}
|
|
1328
1529
|
} else {
|
|
1329
1530
|
if (document.readyState === 'loading') {
|
|
1330
1531
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1349,6 +1550,40 @@
|
|
|
1349
1550
|
// 重新检测P3支持(如果配置变化)
|
|
1350
1551
|
detectP3Support: detectP3Support,
|
|
1351
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
|
+
|
|
1352
1587
|
// 数学计算工具方法
|
|
1353
1588
|
math: {
|
|
1354
1589
|
/**
|
|
@@ -1526,7 +1761,13 @@
|
|
|
1526
1761
|
|
|
1527
1762
|
// 如果传入CSS文本,则编译并返回结果
|
|
1528
1763
|
if (typeof firstArg === 'string') {
|
|
1529
|
-
|
|
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;
|
|
1530
1771
|
}
|
|
1531
1772
|
|
|
1532
1773
|
// 如果传入配置对象,则应用配置
|
|
@@ -1565,7 +1806,13 @@
|
|
|
1565
1806
|
}
|
|
1566
1807
|
|
|
1567
1808
|
// 使用convert函数处理
|
|
1568
|
-
|
|
1809
|
+
const result = convert(cssText, defaultConfig);
|
|
1810
|
+
|
|
1811
|
+
// 如果结果是Promise,返回Promise
|
|
1812
|
+
if (result && typeof result.then === 'function') {
|
|
1813
|
+
return result;
|
|
1814
|
+
}
|
|
1815
|
+
return result;
|
|
1569
1816
|
}
|
|
1570
1817
|
|
|
1571
1818
|
// 将API的所有方法复制到主函数上
|
package/styimat.min.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* 支持 Display P3 广色域显示器
|
|
11
11
|
* 增强 math() 函数,支持复杂数学计算
|
|
12
12
|
* 支持配置头
|
|
13
|
+
* 支持 @import 语法
|
|
13
14
|
*/
|
|
14
15
|
(function(root,factory){if(typeof define==="function"&&define.amd){
|
|
15
16
|
// AMD 支持 (RequireJS)
|
|
@@ -20,9 +21,14 @@ module.exports=factory()}else{
|
|
|
20
21
|
root.styimat=factory()}})(typeof self!=="undefined"?self:this,function(){
|
|
21
22
|
// 默认配置
|
|
22
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()函数增强
|
|
23
|
-
mathPrecision:6
|
|
24
|
+
mathPrecision:6,// 数学计算精度
|
|
25
|
+
importBaseUrl:"",// @import 的基础URL
|
|
26
|
+
importCache:true,// 缓存导入的文件
|
|
27
|
+
importTimeout:5e3};
|
|
24
28
|
// 全局P3支持检测结果
|
|
25
29
|
let p3Supported=null;
|
|
30
|
+
// 导入文件缓存
|
|
31
|
+
const importCache=new Map;
|
|
26
32
|
/**
|
|
27
33
|
* 解析配置头
|
|
28
34
|
* @param {string} cssText - CSS文本
|
|
@@ -418,45 +424,134 @@ result=result.replace(/(?:\$\$|\$)([a-zA-Z0-9_-]+)/g,(match,varName)=>match.star
|
|
|
418
424
|
// 最后处理所有的LAB、LCH颜色和math()函数
|
|
419
425
|
result=processCSSValue(result,config);return result}
|
|
420
426
|
// 生成根规则
|
|
421
|
-
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`}
|
|
422
428
|
// 处理选择器内部的变量
|
|
423
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){
|
|
424
430
|
// 在选择器结束前插入变量声明
|
|
425
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}
|
|
426
432
|
// 使用全局处理函数处理所有math()和颜色
|
|
427
433
|
result+=processCSSValue(line,config)+"\n"}return result.trim()}
|
|
428
|
-
|
|
429
|
-
|
|
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)=>{
|
|
430
484
|
// 先解析配置头
|
|
431
|
-
const{config:headerConfig,css:cleanedCSS}=parseConfigHeader(cssText);const
|
|
485
|
+
const{config:headerConfig,css:cleanedCSS}=parseConfigHeader(cssText);const finalConfig={...defaultConfig,...customConfig,...headerConfig};
|
|
432
486
|
// 1. 提取变量定义(区分全局和选择器局部)
|
|
433
|
-
const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(cleanedCSS,
|
|
487
|
+
const{globalVariables:globalVariables,selectorVariables:selectorVariables,cssWithoutVars:cssWithoutVars}=extractVariablesAndCSS(cleanedCSS,finalConfig);
|
|
434
488
|
// 2. 解析嵌套规则(如果启用)
|
|
435
|
-
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)}}
|
|
436
490
|
// 3. 生成根规则(全局变量)
|
|
437
|
-
const rootRule=generateRootRule(globalVariables,
|
|
491
|
+
const rootRule=generateRootRule(globalVariables,finalConfig);
|
|
438
492
|
// 4. 注入选择器局部变量
|
|
439
|
-
let finalCSS=processedCSS;if(selectorVariables.size>0){finalCSS=injectSelectorVariables(processedCSS,selectorVariables,
|
|
493
|
+
let finalCSS=processedCSS;if(selectorVariables.size>0){finalCSS=injectSelectorVariables(processedCSS,selectorVariables,finalConfig)}
|
|
440
494
|
// 5. 替换变量使用
|
|
441
|
-
finalCSS=replaceVariableUses(finalCSS,globalVariables,selectorVariables,
|
|
495
|
+
finalCSS=replaceVariableUses(finalCSS,globalVariables,selectorVariables,finalConfig);
|
|
442
496
|
// 6. 组合结果
|
|
443
|
-
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)}})()}}
|
|
444
510
|
// 自动处理带有 e 属性的 style 标签
|
|
445
|
-
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);
|
|
446
514
|
// 创建新的 style 标签
|
|
447
515
|
const newStyleTag=document.createElement("style");newStyleTag.textContent=convertedCSS;
|
|
448
516
|
// 插入到原标签后面
|
|
449
517
|
styleTag.parentNode.insertBefore(newStyleTag,styleTag.nextSibling);
|
|
450
518
|
// 可选:移除原标签
|
|
451
|
-
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}}
|
|
452
520
|
// 初始化自动处理
|
|
453
|
-
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)}}}
|
|
454
528
|
// 返回公共 API
|
|
455
529
|
const api={convert:convert,apply:apply,config:config,version:"1.9.0",
|
|
456
530
|
// 检测P3支持
|
|
457
531
|
supportsP3:detectP3Support(),
|
|
458
532
|
// 重新检测P3支持(如果配置变化)
|
|
459
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}},
|
|
460
555
|
// 数学计算工具方法
|
|
461
556
|
math:{
|
|
462
557
|
/**
|
|
@@ -516,7 +611,9 @@ return handleTemplateTag(...args)}
|
|
|
516
611
|
// 获取第一个参数
|
|
517
612
|
const firstArg=args[0];
|
|
518
613
|
// 如果传入CSS文本,则编译并返回结果
|
|
519
|
-
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}
|
|
520
617
|
// 如果传入配置对象,则应用配置
|
|
521
618
|
if(typeof firstArg==="object"&&firstArg!==null){defaultConfig={...defaultConfig,...firstArg};return styimat}
|
|
522
619
|
// 如果没有参数,执行自动处理
|
|
@@ -528,7 +625,9 @@ let cssText=strings[0];for(let i=0;i<values.length;i++){
|
|
|
528
625
|
// 处理插值(支持字符串、数字、函数等)
|
|
529
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]}
|
|
530
627
|
// 使用convert函数处理
|
|
531
|
-
|
|
628
|
+
const result=convert(cssText,defaultConfig);
|
|
629
|
+
// 如果结果是Promise,返回Promise
|
|
630
|
+
if(result&&typeof result.then==="function"){return result}return result}
|
|
532
631
|
// 将API的所有方法复制到主函数上
|
|
533
632
|
Object.assign(styimat,api);Object.setPrototypeOf(styimat,Function.prototype);
|
|
534
633
|
// 自动初始化
|