pug-site-core 3.0.0 → 3.0.2

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