pug-site-core 2.0.22 → 3.0.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/index.js +5 -0
- package/lib/ai/aiPanel.js +0 -0
- package/lib/ai/aiRouter.js +628 -0
- package/lib/debug/debugRouter.js +548 -0
- package/lib/debug/pugDebug.js +2104 -0
- package/lib/devServer.js +26 -5
- package/lib/generate.js +742 -213
- package/lib/paths.js +4 -0
- package/lib/utils.js +1 -7
- package/package.json +8 -4
package/lib/generate.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import fse from "fs-extra";
|
|
2
2
|
import pug from "pug";
|
|
3
|
+
import pugLexer from "pug-lexer";
|
|
4
|
+
import pugParser from "pug-parser";
|
|
5
|
+
import pugWalk from "pug-walk";
|
|
3
6
|
import {
|
|
4
7
|
getPagesPugFilePathArr,
|
|
5
8
|
getCompilePugFilter,
|
|
@@ -13,8 +16,10 @@ import _ from "lodash";
|
|
|
13
16
|
import async from "async";
|
|
14
17
|
import UglifyJS from "uglify-js";
|
|
15
18
|
import { paths } from "./paths.js";
|
|
19
|
+
import path from "path";
|
|
16
20
|
|
|
17
21
|
const { config } = await import(paths.config);
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* 将pages目录下的pug模板编译为JS函数
|
|
20
25
|
* @param {string} pugPath - 指定编译的pug文件路径(相对于/template/pages)
|
|
@@ -51,7 +56,11 @@ export async function compilePagesPugToFn(pugPath) {
|
|
|
51
56
|
10, // 限制并发数为10
|
|
52
57
|
async (fileName) => {
|
|
53
58
|
const filePath = paths.resolveRoot(paths.template.pages, fileName);
|
|
54
|
-
const funName = fileName
|
|
59
|
+
const funName = fileName
|
|
60
|
+
.split(pathSymbol)
|
|
61
|
+
.join("_")
|
|
62
|
+
.slice(0, -4)
|
|
63
|
+
.replace(/[-]/g, "_");
|
|
55
64
|
|
|
56
65
|
// 读取并编译pug文件
|
|
57
66
|
const pugValue = await fse.readFile(filePath, "utf8");
|
|
@@ -141,7 +150,9 @@ export async function generateGetDataFn() {
|
|
|
141
150
|
// 为每个页面注入数据获取函数
|
|
142
151
|
await async.each(pagesPugFilePathArr, async (fileName) => {
|
|
143
152
|
const funName =
|
|
144
|
-
"get_" +
|
|
153
|
+
"get_" +
|
|
154
|
+
fileName.split(pathSymbol).join("_").slice(0, -4).replace(/[-]/g, "_") +
|
|
155
|
+
"_data";
|
|
145
156
|
|
|
146
157
|
// 使用正则表达式检查特定的数据获取函数是否存在
|
|
147
158
|
const dataFnRegex = new RegExp(
|
|
@@ -241,7 +252,7 @@ export async function fetchDataToJsonFile(args) {
|
|
|
241
252
|
// 创建一个全局任务队列控制整体并发数
|
|
242
253
|
const queue = async.queue(async (task) => {
|
|
243
254
|
await task();
|
|
244
|
-
}, fetchDataConcurrencyLimit ||
|
|
255
|
+
}, fetchDataConcurrencyLimit || 3);
|
|
245
256
|
|
|
246
257
|
// 收集所有需要执行的任务
|
|
247
258
|
const allTasks = [];
|
|
@@ -263,6 +274,8 @@ export async function fetchDataToJsonFile(args) {
|
|
|
263
274
|
allTasks.push(async () => {
|
|
264
275
|
console.log(language, commonFuncName, "开始写入json文件");
|
|
265
276
|
const commonData = await getData[commonFuncName](language);
|
|
277
|
+
let languageData = (await import(paths.languageData)).default[language];
|
|
278
|
+
commonData.lang = _.merge(commonData.lang, languageData);
|
|
266
279
|
await fse.outputJSON(
|
|
267
280
|
paths.resolveRoot("jsonData", language, "_common.json"),
|
|
268
281
|
commonData
|
|
@@ -327,7 +340,9 @@ export async function fetchDataToJsonFile(args) {
|
|
|
327
340
|
// 处理模板页面数据
|
|
328
341
|
for (const fileName of pugFilePathList) {
|
|
329
342
|
let funName =
|
|
330
|
-
"get_" +
|
|
343
|
+
"get_" +
|
|
344
|
+
fileName.split(pathSymbol).join("_").slice(0, -4).replace(/[-]/g, "_") +
|
|
345
|
+
"_data";
|
|
331
346
|
|
|
332
347
|
let jsonFilePath = fileName.slice(0, -4).split(pathSymbol);
|
|
333
348
|
if (!getData[funName] || typeof getData[funName] !== "function") {
|
|
@@ -474,244 +489,758 @@ export async function buildFn() {
|
|
|
474
489
|
console.log("打包完成花费:", (Date.now() - starTime) / 1000, "s");
|
|
475
490
|
}
|
|
476
491
|
|
|
477
|
-
//html文件打包
|
|
492
|
+
//html文件打包
|
|
478
493
|
export async function buildStatic() {
|
|
479
494
|
let jsonDataPath = paths.resolveRoot("jsonData");
|
|
480
495
|
|
|
481
|
-
if (!fse.pathExistsSync(jsonDataPath)) {
|
|
482
|
-
return Promise.reject(
|
|
483
|
-
new Error(jsonDataPath + "目录不存在请先执行npm run getData生成数据!")
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
496
|
console.log("开始打包...");
|
|
487
497
|
let starTime = Date.now();
|
|
488
498
|
let distOutputPath = paths.resolveRoot(config.staticOutput);
|
|
489
|
-
await fse.remove(distOutputPath);
|
|
490
|
-
await sleep(0);
|
|
491
|
-
await fse.copy(paths.public, distOutputPath);
|
|
492
499
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
/**
|
|
501
|
+
* 构建HTML文件路径
|
|
502
|
+
* @param {string} lang - 语言代码
|
|
503
|
+
* @param {string} outputPath - 输出路径
|
|
504
|
+
* @param {string} fileName - 文件名(可选)
|
|
505
|
+
* @returns {string} 完整的HTML文件路径
|
|
506
|
+
*/
|
|
507
|
+
function buildHtmlPath(lang, outputPath, fileName = null) {
|
|
508
|
+
let htmlPath
|
|
509
|
+
try {
|
|
510
|
+
if (fileName) {
|
|
511
|
+
htmlPath = paths.resolveRoot(
|
|
512
|
+
distOutputPath,
|
|
513
|
+
lang,
|
|
514
|
+
outputPath.replace(
|
|
515
|
+
outputPath.split("/").pop().replace(/\..*$/, ""),
|
|
516
|
+
fileName.replace(/\..*$/, "")
|
|
517
|
+
)
|
|
518
|
+
);
|
|
519
|
+
} else {
|
|
520
|
+
htmlPath = paths.resolveRoot(distOutputPath, lang, outputPath);
|
|
521
|
+
}
|
|
522
|
+
console.log(htmlPath);
|
|
523
|
+
return htmlPath;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw new Error(`构建HTML路径失败 [lang: ${lang}, outputPath: ${outputPath}, fileName: ${fileName}]: ${error.message}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* 生成HTML内容
|
|
531
|
+
*/
|
|
532
|
+
function generateHtml(pagesPugToFn, funName, data, pagePath, commonData) {
|
|
533
|
+
try {
|
|
534
|
+
if (!pagesPugToFn[funName]) {
|
|
535
|
+
throw new Error(`模板函数 ${funName} 不存在`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return pagesPugToFn[funName]({
|
|
539
|
+
data,
|
|
540
|
+
_pagePath: pagePath,
|
|
541
|
+
common: commonData,
|
|
542
|
+
});
|
|
543
|
+
} catch (error) {
|
|
544
|
+
throw new Error(`生成HTML失败 [funName: ${funName}, pagePath: ${pagePath}]: ${error.message}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* 处理数组数据的HTML输出
|
|
550
|
+
*/
|
|
551
|
+
async function processArrayData(pagesPugToFn, data, obj, funName, pagePath, commonData, lang) {
|
|
552
|
+
try {
|
|
553
|
+
const name = obj.outPutHtmlPath.split("/").pop().replace(/\..*$/, "");
|
|
554
|
+
const regex = /^\[.+\]$/;
|
|
555
|
+
|
|
556
|
+
if (regex.test(name)) {
|
|
557
|
+
const property = name.slice(1, -1);
|
|
558
|
+
|
|
559
|
+
await async.eachLimit(data, 12, async (dataItem, index) => {
|
|
560
|
+
try {
|
|
561
|
+
const fileName = dataItem[property];
|
|
562
|
+
if (!fileName) {
|
|
563
|
+
throw new Error(
|
|
564
|
+
`数据项索引 ${index} 中缺少属性 ${property} 或值为空`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const htmlPath = buildHtmlPath(lang, obj.outPutHtmlPath, fileName);
|
|
569
|
+
const html = generateHtml(pagesPugToFn, funName, dataItem, pagePath, commonData);
|
|
570
|
+
await fse.outputFile(htmlPath, html);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
throw new Error(`处理数组数据项失败 [index: ${index}, property: ${property}]: ${error.message}`);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
} else {
|
|
576
|
+
const htmlPath = buildHtmlPath(lang, obj.outPutHtmlPath);
|
|
577
|
+
const html = generateHtml(pagesPugToFn, funName, data, pagePath, commonData);
|
|
578
|
+
await fse.outputFile(htmlPath, html);
|
|
579
|
+
}
|
|
580
|
+
} catch (error) {
|
|
581
|
+
throw new Error(`处理数组数据失败 [lang: ${lang}, getDataFn: ${obj.getDataFn}]: ${error.message}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* 处理对象数据的HTML输出
|
|
587
|
+
*/
|
|
588
|
+
async function processObjectData(pagesPugToFn, data, obj, funName, pagePath, commonData, lang) {
|
|
589
|
+
try {
|
|
590
|
+
const htmlPath = buildHtmlPath(lang, obj.outPutHtmlPath);
|
|
591
|
+
const html = generateHtml(pagesPugToFn, funName, data, pagePath, commonData);
|
|
592
|
+
await fse.outputFile(htmlPath, html);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
throw new Error(`处理对象数据失败 [lang: ${lang}, getDataFn: ${obj.getDataFn}]: ${error.message}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* 处理自定义HTML构建
|
|
600
|
+
*/
|
|
601
|
+
async function processCustomBuildHtml(pagesPugToFn, lang, getData, commonData, dealWithEndFunName) {
|
|
602
|
+
if (!config.customBuildHtml?.length) return;
|
|
603
|
+
|
|
604
|
+
for (const obj of config.customBuildHtml) {
|
|
605
|
+
try {
|
|
606
|
+
// 检查语言过滤
|
|
607
|
+
if (obj.includeLang?.length > 0 && !obj.includeLang.includes(lang)) {
|
|
608
|
+
continue;
|
|
501
609
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
});
|
|
610
|
+
|
|
611
|
+
// 获取数据
|
|
612
|
+
if (!getData[obj.getDataFn] || typeof getData[obj.getDataFn] !== 'function') {
|
|
613
|
+
throw new Error(`数据获取函数 ${obj.getDataFn} 不存在或不是函数`);
|
|
506
614
|
}
|
|
507
|
-
return true;
|
|
508
|
-
},
|
|
509
|
-
}
|
|
510
|
-
);
|
|
511
615
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
616
|
+
const data = await getData[obj.getDataFn](lang);
|
|
617
|
+
|
|
618
|
+
// 处理路径和函数名
|
|
619
|
+
obj.inputPugPath = obj.inputPugPath.replace(/^\//, "");
|
|
620
|
+
const funName = obj.inputPugPath.split("/").join("_").slice(0, -4);
|
|
621
|
+
dealWithEndFunName.set(funName, 1);
|
|
622
|
+
const pagePath = obj.inputPugPath.replaceAll("/", pathSymbol);
|
|
515
623
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
624
|
+
// 根据数据类型处理
|
|
625
|
+
if (Array.isArray(data)) {
|
|
626
|
+
await processArrayData(pagesPugToFn, data, obj, funName, pagePath, commonData, lang);
|
|
627
|
+
} else if (typeof data === "object" && data !== null) {
|
|
628
|
+
await processObjectData(pagesPugToFn, data, obj, funName, pagePath, commonData, lang);
|
|
629
|
+
} else {
|
|
630
|
+
throw new Error(`数据类型不支持: ${typeof data}`);
|
|
631
|
+
}
|
|
632
|
+
} catch (error) {
|
|
633
|
+
throw new Error(`处理自定义HTML构建失败 [lang: ${lang}, config: ${JSON.stringify(obj)}]: ${error.message}`);
|
|
634
|
+
}
|
|
524
635
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* 处理页面JSON文件
|
|
640
|
+
*/
|
|
641
|
+
async function processPageJsonFiles(pagesPugToFn, lang, langDataPath, commonData, dealWithEndFunName) {
|
|
642
|
+
try {
|
|
643
|
+
const pagesAllJsonFileName = (
|
|
644
|
+
await fse.readdir(langDataPath, { recursive: true })
|
|
645
|
+
).filter((fileName) => fileName.endsWith(".json"));
|
|
646
|
+
|
|
647
|
+
await async.eachLimit(pagesAllJsonFileName, 12, async (jsonFileName) => {
|
|
648
|
+
try {
|
|
649
|
+
const data = await fse.readJSON(paths.resolveRoot(langDataPath, jsonFileName));
|
|
650
|
+
const pugTemplate = data._template;
|
|
651
|
+
|
|
652
|
+
if (!pugTemplate) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const funName = pugTemplate.split(pathSymbol).join("_").slice(0, -4);
|
|
657
|
+
|
|
658
|
+
if (dealWithEndFunName.has(funName)) {
|
|
543
659
|
return;
|
|
544
660
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
661
|
+
|
|
662
|
+
const html = generateHtml(pagesPugToFn, funName, data, pugTemplate, commonData);
|
|
663
|
+
|
|
664
|
+
// 构建输出路径
|
|
665
|
+
let finalPugTemplate = pugTemplate;
|
|
666
|
+
if (data.page_name) {
|
|
667
|
+
finalPugTemplate =
|
|
668
|
+
pugTemplate.split(pathSymbol).slice(0, -1).join(pathSymbol) +
|
|
669
|
+
pathSymbol +
|
|
670
|
+
data.page_name;
|
|
550
671
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
672
|
+
|
|
673
|
+
const htmlPath = paths.resolveRoot(
|
|
674
|
+
distOutputPath,
|
|
675
|
+
lang,
|
|
676
|
+
finalPugTemplate.replace(/\..*$/, ".html")
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
await fse.outputFile(htmlPath, html);
|
|
680
|
+
} catch (error) {
|
|
681
|
+
throw new Error(`处理JSON文件失败 [file: ${jsonFileName}]: ${error.message}`);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
} catch (error) {
|
|
685
|
+
throw new Error(`处理页面JSON文件失败 [lang: ${lang}]: ${error.message}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
const getData = await import(paths.getData);
|
|
691
|
+
|
|
692
|
+
// 清理并准备输出目录
|
|
693
|
+
await fse.remove(distOutputPath);
|
|
694
|
+
await sleep(0);
|
|
695
|
+
await fse.copy(paths.public, distOutputPath);
|
|
696
|
+
|
|
697
|
+
// 复制静态资源
|
|
698
|
+
await fse.copy(
|
|
699
|
+
paths.template.static,
|
|
700
|
+
paths.resolveRoot(distOutputPath, "static"),
|
|
701
|
+
{
|
|
702
|
+
filter: (src, dest) => {
|
|
703
|
+
//根目录必须要返回true
|
|
704
|
+
if (src.endsWith("static")) {
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
if (config.buildStaticDirArr && config.buildStaticDirArr.length > 0) {
|
|
708
|
+
return !!config.buildStaticDirArr.find((item) => {
|
|
709
|
+
return src.startsWith(paths.resolveRoot(paths.template.static, item));
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return true;
|
|
713
|
+
},
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// 编译PUG模板
|
|
718
|
+
await compilePagesPugToFn();
|
|
719
|
+
const PagesPugToFn = await import(paths.pagesPugFn);
|
|
720
|
+
|
|
721
|
+
// 处理每种语言
|
|
722
|
+
await async.each(config.languageList, async (lang) => {
|
|
723
|
+
try {
|
|
724
|
+
console.log("开始处理语言:", lang);
|
|
725
|
+
const langDataPath = paths.resolveRoot(jsonDataPath, lang);
|
|
726
|
+
|
|
727
|
+
// 读取公共数据
|
|
728
|
+
let commonData;
|
|
729
|
+
try {
|
|
730
|
+
commonData = await fse.readJSON(
|
|
731
|
+
paths.resolveRoot("jsonData", lang, "_common.json")
|
|
732
|
+
);
|
|
733
|
+
commonData = _.merge(commonData, config.commonData);
|
|
734
|
+
} catch (error) {
|
|
735
|
+
throw new Error(`读取公共数据失败: ${error.message}`);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const dealWithEndFunName = new Map();
|
|
739
|
+
|
|
740
|
+
// 处理自定义HTML构建
|
|
741
|
+
await processCustomBuildHtml(PagesPugToFn, lang, getData, commonData, dealWithEndFunName);
|
|
742
|
+
|
|
743
|
+
// 处理页面JSON文件
|
|
744
|
+
await processPageJsonFiles(PagesPugToFn, lang, langDataPath, commonData, dealWithEndFunName);
|
|
745
|
+
|
|
746
|
+
} catch (error) {
|
|
747
|
+
throw new Error(`处理语言 ${lang} 失败: ${error.message}`);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// 混淆JavaScript文件(如果配置了)
|
|
752
|
+
if (config.obfuscateJavaScript) {
|
|
753
|
+
console.log("开始混淆js文件...");
|
|
754
|
+
const startTime = Date.now();
|
|
755
|
+
try {
|
|
756
|
+
await obfuscateJavaScript(paths.resolveRoot(distOutputPath, "static"));
|
|
757
|
+
const costTime = (Date.now() - startTime) / 1000;
|
|
758
|
+
console.log("混淆js文件耗时:", costTime, "s");
|
|
759
|
+
} catch (error) {
|
|
760
|
+
throw new Error(`混淆JavaScript文件失败: ${error.message}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
console.log("打包完成花费:", (Date.now() - starTime) / 1000, "s");
|
|
765
|
+
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error("HTML文件打包失败:", error);
|
|
768
|
+
throw error;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* 创建调试模板目录
|
|
774
|
+
* 复制template目录到template-debug并为所有pug文件添加调试属性
|
|
775
|
+
* @returns {Promise<void>}
|
|
776
|
+
*/
|
|
777
|
+
export async function createDebugTemplate() {
|
|
778
|
+
try {
|
|
779
|
+
console.log("开始创建调试模板目录...");
|
|
780
|
+
|
|
781
|
+
const sourceDir = paths.template.root;
|
|
782
|
+
const targetDir = paths.template.debug;
|
|
783
|
+
|
|
784
|
+
// 删除已存在的调试目录
|
|
785
|
+
if (await fse.pathExists(targetDir)) {
|
|
786
|
+
await fse.remove(targetDir);
|
|
787
|
+
console.log("已删除现有的调试目录");
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// 复制整个 template 目录到 template-debug
|
|
791
|
+
await fse.copy(sourceDir, targetDir);
|
|
792
|
+
console.log(`已复制 ${sourceDir} 到 ${targetDir}`);
|
|
793
|
+
|
|
794
|
+
let languageData = (await import(paths.languageData)).default.us;
|
|
795
|
+
|
|
796
|
+
// 缓存语言路径检查结果,避免重复计算
|
|
797
|
+
const langPathCache = new Map();
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* 检查属性路径是否在languageData的us对象中存在
|
|
801
|
+
* @param {string} langPath - 属性路径,例如 "Store.about"
|
|
802
|
+
* @returns {boolean} 是否存在该属性
|
|
803
|
+
*/
|
|
804
|
+
function checkLangPathExists(langPath) {
|
|
805
|
+
// 使用缓存提升性能
|
|
806
|
+
if (langPathCache.has(langPath)) {
|
|
807
|
+
return langPathCache.get(langPath);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
try {
|
|
811
|
+
const keys = langPath.split(".");
|
|
812
|
+
let current = languageData;
|
|
813
|
+
|
|
814
|
+
for (const key of keys) {
|
|
815
|
+
if (current && typeof current === "object" && key in current) {
|
|
816
|
+
current = current[key];
|
|
817
|
+
} else {
|
|
818
|
+
langPathCache.set(langPath, false);
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const isValid = typeof current === "string";
|
|
824
|
+
langPathCache.set(langPath, isValid);
|
|
825
|
+
return isValid;
|
|
826
|
+
} catch (error) {
|
|
827
|
+
langPathCache.set(langPath, false);
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* 检查是否是动态内容
|
|
834
|
+
* @param {string} code - 代码内容
|
|
835
|
+
* @returns {boolean} 是否包含动态数据
|
|
836
|
+
*/
|
|
837
|
+
function isDynamicContent(code) {
|
|
838
|
+
// 更精确的动态内容检测
|
|
839
|
+
const dynamicPatterns = [
|
|
840
|
+
/\b(Math|Date|JSON|parseInt|parseFloat|Number|String|Array|Object)\b/,
|
|
841
|
+
/\b(data|item|index|info|common\.siteConfig)\b/,
|
|
842
|
+
/[+\-*/%]/,
|
|
843
|
+
/\brandom\b|\bfloor\b|\bceil\b|\bround\b/,
|
|
844
|
+
/\.\w+\(/, // 方法调用
|
|
845
|
+
/\$\{[^}]*[+\-*/%.][^}]*\}/, // 模板字符串中的计算
|
|
846
|
+
];
|
|
847
|
+
|
|
848
|
+
return dynamicPatterns.some((pattern) => pattern.test(code));
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* 提取Pug节点中的common.lang引用
|
|
853
|
+
* @param {string} code - 代码内容
|
|
854
|
+
* @returns {string|null} 语言路径,如果不是common.lang引用则返回null
|
|
855
|
+
*/
|
|
856
|
+
function extractLangPath(code) {
|
|
857
|
+
// 匹配 common.lang.xxx
|
|
858
|
+
const langMatch = code.match(/common\.lang\.(.+)/);
|
|
859
|
+
if (langMatch) {
|
|
860
|
+
return langMatch[1];
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// 匹配 ${common.lang.xxx}
|
|
864
|
+
const templateLangMatch = code.match(/\$\{common\.lang\.([^}]+)\}/);
|
|
865
|
+
if (templateLangMatch) {
|
|
866
|
+
return templateLangMatch[1];
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// 匹配带反引号的模板字符串 `${common.lang.xxx}`
|
|
870
|
+
const backquoteLangMatch = code.match(/`.*\$\{common\.lang\.([^}]+)\}.*`/);
|
|
871
|
+
if (backquoteLangMatch) {
|
|
872
|
+
return backquoteLangMatch[1];
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* 使用 pug-parser 和 pug-walk 分析 pug 文件中的标签
|
|
880
|
+
* @param {string} pugContent - pug 文件内容
|
|
881
|
+
* @param {string} filename - 文件名
|
|
882
|
+
* @returns {Map} 行号到标签信息的映射
|
|
883
|
+
*/
|
|
884
|
+
function analyzeTagsFromPug(pugContent, filename) {
|
|
885
|
+
try {
|
|
886
|
+
const tokens = pugLexer(pugContent, { filename });
|
|
887
|
+
const ast = pugParser(tokens, { filename, src: pugContent });
|
|
888
|
+
|
|
889
|
+
const lineTagMap = new Map();
|
|
890
|
+
|
|
891
|
+
pugWalk(ast, function before(node) {
|
|
892
|
+
if (node.type === "Tag" && node.line) {
|
|
893
|
+
const tagInfo = {
|
|
894
|
+
tagName: node.name,
|
|
895
|
+
line: node.line,
|
|
896
|
+
isEditable: false,
|
|
897
|
+
editableValue: "true",
|
|
898
|
+
textContent: "",
|
|
899
|
+
langPath: "",
|
|
900
|
+
hasStaticText: false,
|
|
901
|
+
hasLangText: false,
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
// 分析标签的子节点来确定文本内容
|
|
905
|
+
if (node.block && node.block.nodes) {
|
|
906
|
+
for (const child of node.block.nodes) {
|
|
907
|
+
// 处理Text节点(静态文本)
|
|
908
|
+
if (child.type === "Text" && child.val && child.val.trim()) {
|
|
909
|
+
// 检查是否在同一行还有其他动态内容
|
|
910
|
+
const hasOtherDynamicNodes = node.block.nodes.some(
|
|
911
|
+
(sibling) =>
|
|
912
|
+
sibling !== child &&
|
|
913
|
+
sibling.line === child.line &&
|
|
914
|
+
(sibling.type === "Code" ||
|
|
915
|
+
sibling.type === "InterpolatedTag") &&
|
|
916
|
+
sibling.val &&
|
|
917
|
+
isDynamicContent(sibling.val)
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
if (!hasOtherDynamicNodes) {
|
|
921
|
+
tagInfo.hasStaticText = true;
|
|
922
|
+
tagInfo.textContent = child.val.trim();
|
|
923
|
+
tagInfo.isEditable = true;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// 处理Code节点(插值表达式和代码块)
|
|
928
|
+
if (child.type === "Code" && child.val) {
|
|
929
|
+
const langPath = extractLangPath(child.val);
|
|
930
|
+
if (langPath && !isDynamicContent(child.val)) {
|
|
931
|
+
if (checkLangPathExists(langPath)) {
|
|
932
|
+
tagInfo.hasLangText = true;
|
|
933
|
+
tagInfo.langPath = langPath;
|
|
934
|
+
tagInfo.isEditable = true;
|
|
935
|
+
// 转换为 [us][key1][key2] 格式
|
|
936
|
+
const pathParts = langPath.split(".");
|
|
937
|
+
tagInfo.editableValue = `us,${pathParts.join(",")}`;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
560
942
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
943
|
+
|
|
944
|
+
// 检查节点本身是否有代码内容(对于!=语法)
|
|
945
|
+
if (node.code && node.code.val) {
|
|
946
|
+
const langPath = extractLangPath(node.code.val);
|
|
947
|
+
if (langPath && !isDynamicContent(node.code.val)) {
|
|
948
|
+
if (checkLangPathExists(langPath)) {
|
|
949
|
+
tagInfo.hasLangText = true;
|
|
950
|
+
tagInfo.langPath = langPath;
|
|
951
|
+
tagInfo.isEditable = true;
|
|
952
|
+
// 转换为 us,key1,key2 格式
|
|
953
|
+
const pathParts = langPath.split(".");
|
|
954
|
+
tagInfo.editableValue = `us,${pathParts.join(",")}`;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
564
957
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
958
|
+
|
|
959
|
+
// 将标签信息添加到行映射中
|
|
960
|
+
if (!lineTagMap.has(node.line)) {
|
|
961
|
+
lineTagMap.set(node.line, []);
|
|
568
962
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
963
|
+
lineTagMap.get(node.line).push(tagInfo);
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
return lineTagMap;
|
|
968
|
+
} catch (error) {
|
|
969
|
+
console.error(`分析 pug 文件 ${filename} 时出错:`, error);
|
|
970
|
+
return new Map();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* 为 pug 文件内容添加调试属性
|
|
976
|
+
* @param {string} pugContent - pug 文件内容
|
|
977
|
+
* @param {string} relativePath - 文件相对路径
|
|
978
|
+
* @param {string} filename - 文件名
|
|
979
|
+
* @returns {string} 处理后的 pug 内容
|
|
980
|
+
*/
|
|
981
|
+
function addDebugAttributesToPug(pugContent, relativePath, filename) {
|
|
982
|
+
try {
|
|
983
|
+
const lines = pugContent.split("\n");
|
|
984
|
+
const lineTagMap = analyzeTagsFromPug(pugContent, filename);
|
|
985
|
+
|
|
986
|
+
// 定义不需要添加调试属性的标签
|
|
987
|
+
const excludeTags = new Set([
|
|
988
|
+
"head",
|
|
989
|
+
"title",
|
|
990
|
+
"meta",
|
|
991
|
+
"link",
|
|
992
|
+
"html",
|
|
993
|
+
"style",
|
|
994
|
+
"script",
|
|
995
|
+
"base",
|
|
996
|
+
"noscript",
|
|
997
|
+
"template",
|
|
998
|
+
]);
|
|
999
|
+
|
|
1000
|
+
// 处理每一行
|
|
1001
|
+
const processedLines = lines.map((line, index) => {
|
|
1002
|
+
const lineNumber = index + 1;
|
|
1003
|
+
const tagsInLine = lineTagMap.get(lineNumber);
|
|
1004
|
+
|
|
1005
|
+
if (!tagsInLine || tagsInLine.length === 0) {
|
|
1006
|
+
return line;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// 过滤掉不需要处理的标签
|
|
1010
|
+
const visibleTags = tagsInLine.filter(
|
|
1011
|
+
(tag) => !excludeTags.has(tag.tagName.toLowerCase())
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
if (visibleTags.length === 0) {
|
|
1015
|
+
return line;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// 对于每个标签,添加调试属性
|
|
1019
|
+
let processedLine = line;
|
|
1020
|
+
|
|
1021
|
+
// 只处理第一个标签,避免处理插值表达式中的内容
|
|
1022
|
+
const mainTag = visibleTags[0];
|
|
1023
|
+
|
|
1024
|
+
if (mainTag) {
|
|
1025
|
+
// 构建调试属性
|
|
1026
|
+
const debugAttrs = [
|
|
1027
|
+
`data-debug-file="${relativePath}"`,
|
|
1028
|
+
`data-debug-line="${lineNumber}"`,
|
|
1029
|
+
];
|
|
1030
|
+
|
|
1031
|
+
// 只有可编辑的元素才添加 data-debug-editable 属性
|
|
1032
|
+
if (mainTag.isEditable) {
|
|
1033
|
+
debugAttrs.push(`data-debug-editable="${mainTag.editableValue}"`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
const debugAttrsString = debugAttrs.join(", ");
|
|
1037
|
+
|
|
1038
|
+
// 简化的标签处理逻辑
|
|
1039
|
+
const trimmedLine = line.trim();
|
|
1040
|
+
|
|
1041
|
+
// 处理隐式 div(.class 或 #id 语法)
|
|
1042
|
+
if (trimmedLine.startsWith(".") || trimmedLine.startsWith("#")) {
|
|
1043
|
+
const indent = line.match(/^\s*/)[0];
|
|
1044
|
+
|
|
1045
|
+
// 找到选择器结束的位置
|
|
1046
|
+
let selectorEndIndex = 1;
|
|
1047
|
+
while (
|
|
1048
|
+
selectorEndIndex < trimmedLine.length &&
|
|
1049
|
+
/[\w-]/.test(trimmedLine[selectorEndIndex])
|
|
1050
|
+
) {
|
|
1051
|
+
selectorEndIndex++;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// 检查是否已经有属性括号
|
|
1055
|
+
const afterSelector = trimmedLine.substring(selectorEndIndex);
|
|
1056
|
+
const parenIndex = afterSelector.indexOf("(");
|
|
1057
|
+
|
|
1058
|
+
if (parenIndex !== -1) {
|
|
1059
|
+
// 已经有属性括号
|
|
1060
|
+
const beforeParen = trimmedLine.substring(
|
|
1061
|
+
0,
|
|
1062
|
+
selectorEndIndex + parenIndex + 1
|
|
1063
|
+
);
|
|
1064
|
+
const afterFirstParen = trimmedLine.substring(
|
|
1065
|
+
selectorEndIndex + parenIndex + 1
|
|
1066
|
+
);
|
|
1067
|
+
const lastParenIndex = afterFirstParen.lastIndexOf(")");
|
|
1068
|
+
|
|
1069
|
+
if (lastParenIndex !== -1) {
|
|
1070
|
+
const existingAttrs = afterFirstParen.substring(0, lastParenIndex);
|
|
1071
|
+
const afterLastParen = afterFirstParen.substring(lastParenIndex);
|
|
1072
|
+
|
|
1073
|
+
const separator = existingAttrs.trim() ? ", " : "";
|
|
1074
|
+
processedLine = `${indent}${beforeParen}${existingAttrs}${separator}${debugAttrsString}${afterLastParen}`;
|
|
1075
|
+
}
|
|
1076
|
+
} else {
|
|
1077
|
+
// 没有属性括号,在选择器后添加
|
|
1078
|
+
const selector = trimmedLine.substring(0, selectorEndIndex);
|
|
1079
|
+
const afterAttrs = trimmedLine.substring(selectorEndIndex);
|
|
1080
|
+
|
|
1081
|
+
processedLine = `${indent}${selector}(${debugAttrsString})${afterAttrs}`;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
// 处理普通标签
|
|
1085
|
+
else if (trimmedLine.startsWith(mainTag.tagName)) {
|
|
1086
|
+
const indent = line.match(/^\s*/)[0];
|
|
1087
|
+
|
|
1088
|
+
// 找到标签名和修饰符的结束位置
|
|
1089
|
+
const tagEndIndex = findTagEnd(mainTag.tagName, trimmedLine);
|
|
1090
|
+
const afterTag = trimmedLine.substring(tagEndIndex);
|
|
1091
|
+
|
|
1092
|
+
if (afterTag.startsWith("(")) {
|
|
1093
|
+
// 已经有属性括号,需要找到正确的结束位置
|
|
1094
|
+
const parenEnd = findMatchingParen(trimmedLine, tagEndIndex);
|
|
1095
|
+
|
|
1096
|
+
if (parenEnd !== -1) {
|
|
1097
|
+
const tagWithModifiers = trimmedLine.substring(0, tagEndIndex);
|
|
1098
|
+
const existingAttrs = trimmedLine.substring(
|
|
1099
|
+
tagEndIndex + 1,
|
|
1100
|
+
parenEnd
|
|
599
1101
|
);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
})({
|
|
605
|
-
data: dataItem,
|
|
606
|
-
_pagePath: obj.pugPath,
|
|
607
|
-
common: commonData,
|
|
608
|
-
});
|
|
609
|
-
fse.ensureFileSync(htmlPath);
|
|
610
|
-
await fse.writeFile(htmlPath, html);
|
|
1102
|
+
const remaining = trimmedLine.substring(parenEnd + 1);
|
|
1103
|
+
|
|
1104
|
+
const separator = existingAttrs.trim() ? ", " : "";
|
|
1105
|
+
processedLine = `${indent}${tagWithModifiers}(${existingAttrs}${separator}${debugAttrsString})${remaining}`;
|
|
611
1106
|
}
|
|
612
1107
|
} else {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
);
|
|
619
|
-
html = pug.compileFile(pugPath, {
|
|
620
|
-
basedir: paths.template.root,
|
|
621
|
-
compileDebug: true,
|
|
622
|
-
filters: getCompilePugFilter(),
|
|
623
|
-
})({
|
|
624
|
-
data,
|
|
625
|
-
_pagePath: obj.pugPath,
|
|
626
|
-
common: commonData,
|
|
627
|
-
});
|
|
628
|
-
fse.ensureFileSync(htmlPath);
|
|
629
|
-
await fse.writeFile(htmlPath, html);
|
|
1108
|
+
// 没有属性括号,添加新的属性括号
|
|
1109
|
+
const tagWithModifiers = trimmedLine.substring(0, tagEndIndex);
|
|
1110
|
+
const remaining = trimmedLine.substring(tagEndIndex);
|
|
1111
|
+
|
|
1112
|
+
processedLine = `${indent}${tagWithModifiers}(${debugAttrsString})${remaining}`;
|
|
630
1113
|
}
|
|
631
|
-
} else if (typeof data === "object") {
|
|
632
|
-
htmlPath = paths.resolveRoot(
|
|
633
|
-
distOutputPath,
|
|
634
|
-
lang,
|
|
635
|
-
devicePrefix,
|
|
636
|
-
outPutPath
|
|
637
|
-
);
|
|
638
|
-
html = pug.compileFile(pugPath, {
|
|
639
|
-
basedir: paths.template.root,
|
|
640
|
-
compileDebug: true,
|
|
641
|
-
filters: getCompilePugFilter(),
|
|
642
|
-
})({
|
|
643
|
-
data,
|
|
644
|
-
_pagePath: obj.pugPath,
|
|
645
|
-
common: commonData,
|
|
646
|
-
});
|
|
647
|
-
fse.ensureFileSync(htmlPath);
|
|
648
|
-
await fse.writeFile(htmlPath, html);
|
|
649
1114
|
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return processedLine;
|
|
1118
|
+
});
|
|
654
1119
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
).filter((fileName) => fileName.endsWith(".json"));
|
|
660
|
-
await async.eachLimit(pagesAllJsonFileName, 64, async (jsonFileName) => {
|
|
661
|
-
let data = await fse.readJSON(paths.resolveRoot(langDataPath, jsonFileName));
|
|
662
|
-
let pugTemplateArr = data._template;
|
|
663
|
-
if (!pugTemplateArr) {
|
|
664
|
-
return;
|
|
1120
|
+
return processedLines.join("\n");
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
console.error(`处理 pug 文件 ${filename} 时出错:`, error);
|
|
1123
|
+
return pugContent;
|
|
665
1124
|
}
|
|
666
|
-
|
|
1125
|
+
}
|
|
667
1126
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1127
|
+
/**
|
|
1128
|
+
* 找到标签名和修饰符的结束位置
|
|
1129
|
+
* @param {string} tagName - 标签名
|
|
1130
|
+
* @param {string} line - 行内容
|
|
1131
|
+
* @returns {number} 结束位置索引
|
|
1132
|
+
*/
|
|
1133
|
+
function findTagEnd(tagName, line) {
|
|
1134
|
+
let index = tagName.length;
|
|
1135
|
+
|
|
1136
|
+
// 跳过类名和ID修饰符
|
|
1137
|
+
while (index < line.length) {
|
|
1138
|
+
const char = line[index];
|
|
1139
|
+
if (char === "." || char === "#") {
|
|
1140
|
+
index++;
|
|
1141
|
+
while (index < line.length && /[\w-]/.test(line[index])) {
|
|
1142
|
+
index++;
|
|
683
1143
|
}
|
|
684
|
-
|
|
685
|
-
|
|
1144
|
+
} else {
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
686
1147
|
}
|
|
687
1148
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1149
|
+
return index;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* 找到匹配的右括号
|
|
1154
|
+
* @param {string} str - 字符串
|
|
1155
|
+
* @param {number} start - 开始位置
|
|
1156
|
+
* @returns {number} 匹配括号的位置,-1表示未找到
|
|
1157
|
+
*/
|
|
1158
|
+
function findMatchingParen(str, start) {
|
|
1159
|
+
let depth = 0;
|
|
1160
|
+
let i = start;
|
|
1161
|
+
let inString = false;
|
|
1162
|
+
let stringChar = "";
|
|
1163
|
+
|
|
1164
|
+
while (i < str.length) {
|
|
1165
|
+
const char = str[i];
|
|
1166
|
+
|
|
1167
|
+
// 处理字符串内容,避免字符串中的括号影响匹配
|
|
1168
|
+
if (!inString && (char === '"' || char === "'" || char === "`")) {
|
|
1169
|
+
inString = true;
|
|
1170
|
+
stringChar = char;
|
|
1171
|
+
} else if (inString && char === stringChar && str[i - 1] !== "\\") {
|
|
1172
|
+
inString = false;
|
|
1173
|
+
stringChar = "";
|
|
1174
|
+
} else if (!inString) {
|
|
1175
|
+
if (char === "(") {
|
|
1176
|
+
depth++;
|
|
1177
|
+
} else if (char === ")") {
|
|
1178
|
+
depth--;
|
|
1179
|
+
if (depth === 0) {
|
|
1180
|
+
return i;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
692
1183
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1184
|
+
i++;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
return -1; // 没有找到匹配的括号
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* 递归处理目录中的所有 pug 文件
|
|
1192
|
+
* @param {string} dir - 目录路径
|
|
1193
|
+
* @param {string} baseDir - 基础目录路径(用于计算相对路径)
|
|
1194
|
+
*/
|
|
1195
|
+
async function processPugFilesInDirectory(dir, baseDir) {
|
|
1196
|
+
try {
|
|
1197
|
+
const files = await fse.readdir(dir);
|
|
1198
|
+
|
|
1199
|
+
for (const file of files) {
|
|
1200
|
+
const fullPath = path.join(dir, file);
|
|
1201
|
+
const stats = await fse.stat(fullPath);
|
|
1202
|
+
|
|
1203
|
+
if (stats.isDirectory()) {
|
|
1204
|
+
// 递归处理子目录
|
|
1205
|
+
await processPugFilesInDirectory(fullPath, baseDir);
|
|
1206
|
+
} else if (file.endsWith(".pug")) {
|
|
1207
|
+
// 处理 pug 文件
|
|
1208
|
+
try {
|
|
1209
|
+
const relativePath = path
|
|
1210
|
+
.relative(baseDir, fullPath)
|
|
1211
|
+
.replace(/\\/g, "/");
|
|
1212
|
+
const pugContent = await fse.readFile(fullPath, "utf8");
|
|
1213
|
+
|
|
1214
|
+
console.log(`正在处理: ${relativePath}`);
|
|
1215
|
+
|
|
1216
|
+
const processedContent = addDebugAttributesToPug(
|
|
1217
|
+
pugContent,
|
|
1218
|
+
relativePath,
|
|
1219
|
+
file
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
// 写回处理后的内容
|
|
1223
|
+
await fse.writeFile(fullPath, processedContent, "utf8");
|
|
1224
|
+
|
|
1225
|
+
console.log(`已处理: ${relativePath}`);
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
console.error(`处理文件 ${fullPath} 时出错:`, error);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
703
1230
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
});
|
|
715
|
-
})
|
|
716
|
-
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
console.error(`处理目录 ${dir} 时出错:`, error);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// 开始处理调试目录中的所有 pug 文件
|
|
1237
|
+
console.log("开始为 pug 文件添加调试属性...");
|
|
1238
|
+
await processPugFilesInDirectory(targetDir, targetDir);
|
|
1239
|
+
|
|
1240
|
+
console.log("调试模板目录创建完成!");
|
|
1241
|
+
console.log(`调试模板位置: ${targetDir}`);
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
console.error("创建调试模板时出错:", error);
|
|
1244
|
+
throw error;
|
|
1245
|
+
}
|
|
717
1246
|
}
|