pug-site-core 2.0.22 → 3.0.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/index.js +5 -0
- package/lib/ai/aiPanel.js +722 -0
- package/lib/ai/aiRouter.js +300 -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 +455 -0
- package/lib/paths.js +4 -0
- package/package.json +8 -4
package/lib/devServer.js
CHANGED
|
@@ -16,9 +16,11 @@ import http from "http";
|
|
|
16
16
|
import WebSocket, { WebSocketServer } from "ws";
|
|
17
17
|
import { Worker } from "worker_threads";
|
|
18
18
|
import { paths } from "./paths.js";
|
|
19
|
+
import { injectDebugScript } from "./debug/pugDebug.js";
|
|
20
|
+
import { setupDebugRoutes } from "./debug/debugRouter.js";
|
|
19
21
|
|
|
20
22
|
const { config } = await import(paths.config);
|
|
21
|
-
const pagsTemplatePath = paths.template.pages;
|
|
23
|
+
const pagsTemplatePath = config.devServer.isDebug ? paths.template.debugPages : paths.template.pages;
|
|
22
24
|
const localIp = ip.address();
|
|
23
25
|
const port = await getIdleProt(config.devServer.port);
|
|
24
26
|
process.env._port = port;
|
|
@@ -48,7 +50,7 @@ function setupMiddleware(app) {
|
|
|
48
50
|
app.set("view engine", "pug");
|
|
49
51
|
app.use("/static", express.static(paths.template.static));
|
|
50
52
|
app.use(express.static(paths.public));
|
|
51
|
-
app.locals.basedir = paths.template.root;
|
|
53
|
+
app.locals.basedir = config.devServer.isDebug ? paths.template.debug : paths.template.root;
|
|
52
54
|
|
|
53
55
|
// 设置代理
|
|
54
56
|
setupProxy(app);
|
|
@@ -115,6 +117,9 @@ function setupProxy(app) {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
function setupRoutes(app, wss) {
|
|
120
|
+
// 设置调试相关的路由
|
|
121
|
+
setupDebugRoutes(app);
|
|
122
|
+
|
|
118
123
|
app.get("/_refresh", (req, res) => {
|
|
119
124
|
wss.clients.forEach(function each(client) {
|
|
120
125
|
if (client.readyState === WebSocket.OPEN) {
|
|
@@ -180,6 +185,8 @@ function setupRoutes(app, wss) {
|
|
|
180
185
|
commonData = await (
|
|
181
186
|
await import(paths.getData)
|
|
182
187
|
)["get_common_data"](language);
|
|
188
|
+
let languageData = (await import(paths.languageData)).default[language];
|
|
189
|
+
commonData.lang = _.merge(commonData.lang, languageData);
|
|
183
190
|
await fse.writeJSON(
|
|
184
191
|
paths.resolveRoot("jsonData", language, "_common.json"),
|
|
185
192
|
commonData
|
|
@@ -190,6 +197,7 @@ function setupRoutes(app, wss) {
|
|
|
190
197
|
);
|
|
191
198
|
}
|
|
192
199
|
let _refreshScript = `<script>const ws=new WebSocket('ws://${localIp}:${port}');ws.onmessage=function(event){if(event.data==='refresh'){console.log('Refreshing page...');location.reload()}}</script>`;
|
|
200
|
+
|
|
193
201
|
res.render(
|
|
194
202
|
pugPath,
|
|
195
203
|
_.merge(
|
|
@@ -208,7 +216,16 @@ function setupRoutes(app, wss) {
|
|
|
208
216
|
if (config.isMatchEsi) {
|
|
209
217
|
html = await matchESI(html, data);
|
|
210
218
|
}
|
|
211
|
-
|
|
219
|
+
|
|
220
|
+
if(config.isScopeIsolation){
|
|
221
|
+
html = addTemplateScopeIsolation(html);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 注入调试脚本(仅在开发环境)
|
|
225
|
+
if (config.devServer.isDebug) {
|
|
226
|
+
html = injectDebugScript(html);
|
|
227
|
+
}
|
|
228
|
+
|
|
212
229
|
res.send(_refreshScript + html);
|
|
213
230
|
}
|
|
214
231
|
}
|
|
@@ -225,14 +242,18 @@ function setupRoutes(app, wss) {
|
|
|
225
242
|
}
|
|
226
243
|
|
|
227
244
|
async function matchRouter(url, language, device) {
|
|
228
|
-
let
|
|
245
|
+
let routerModule = await import(paths.router);
|
|
246
|
+
let router = routerModule.router;
|
|
247
|
+
let abtestRouter = routerModule.abtestRouter;
|
|
229
248
|
let getR2Data = async (jsonPath) => {
|
|
230
249
|
jsonPath = paths.join(language, jsonPath);
|
|
231
250
|
let data = await getJsonData(jsonPath);
|
|
232
251
|
return data;
|
|
233
252
|
};
|
|
234
253
|
let params = { url, language, device, getR2Data };
|
|
235
|
-
|
|
254
|
+
if(config.abtest && config.abtest.enabled && abtestRouter && abtestRouter[config.abtest.curVariant]){
|
|
255
|
+
router.unshift(...abtestRouter[config.abtest.curVariant]);
|
|
256
|
+
}
|
|
236
257
|
for (let index = 0; index < router.length; index++) {
|
|
237
258
|
const obj = router[index];
|
|
238
259
|
if (obj.matchFn.call(params)) {
|
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)
|
|
@@ -263,6 +268,8 @@ export async function fetchDataToJsonFile(args) {
|
|
|
263
268
|
allTasks.push(async () => {
|
|
264
269
|
console.log(language, commonFuncName, "开始写入json文件");
|
|
265
270
|
const commonData = await getData[commonFuncName](language);
|
|
271
|
+
let languageData = (await import(paths.languageData)).default[language];
|
|
272
|
+
commonData.lang = _.merge(commonData.lang, languageData);
|
|
266
273
|
await fse.outputJSON(
|
|
267
274
|
paths.resolveRoot("jsonData", language, "_common.json"),
|
|
268
275
|
commonData
|
|
@@ -715,3 +722,451 @@ export async function buildStatic() {
|
|
|
715
722
|
});
|
|
716
723
|
console.log("打包完成花费:", (Date.now() - starTime) / 1000, "s");
|
|
717
724
|
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* 创建调试模板目录
|
|
728
|
+
* 复制template目录到template-debug并为所有pug文件添加调试属性
|
|
729
|
+
* @returns {Promise<void>}
|
|
730
|
+
*/
|
|
731
|
+
export async function createDebugTemplate() {
|
|
732
|
+
try {
|
|
733
|
+
console.log('开始创建调试模板目录...');
|
|
734
|
+
|
|
735
|
+
const sourceDir = paths.template.root;
|
|
736
|
+
const targetDir = paths.template.debug;
|
|
737
|
+
|
|
738
|
+
// 删除已存在的调试目录
|
|
739
|
+
if (await fse.pathExists(targetDir)) {
|
|
740
|
+
await fse.remove(targetDir);
|
|
741
|
+
console.log('已删除现有的调试目录');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// 复制整个 template 目录到 template-debug
|
|
745
|
+
await fse.copy(sourceDir, targetDir);
|
|
746
|
+
console.log(`已复制 ${sourceDir} 到 ${targetDir}`);
|
|
747
|
+
|
|
748
|
+
let languageData = (await import(paths.languageData)).default.us;
|
|
749
|
+
|
|
750
|
+
// 缓存语言路径检查结果,避免重复计算
|
|
751
|
+
const langPathCache = new Map();
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* 检查属性路径是否在languageData的us对象中存在
|
|
755
|
+
* @param {string} langPath - 属性路径,例如 "Store.about"
|
|
756
|
+
* @returns {boolean} 是否存在该属性
|
|
757
|
+
*/
|
|
758
|
+
function checkLangPathExists(langPath) {
|
|
759
|
+
// 使用缓存提升性能
|
|
760
|
+
if (langPathCache.has(langPath)) {
|
|
761
|
+
return langPathCache.get(langPath);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
try {
|
|
765
|
+
const keys = langPath.split('.');
|
|
766
|
+
let current = languageData;
|
|
767
|
+
|
|
768
|
+
for (const key of keys) {
|
|
769
|
+
if (current && typeof current === 'object' && key in current) {
|
|
770
|
+
current = current[key];
|
|
771
|
+
} else {
|
|
772
|
+
langPathCache.set(langPath, false);
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const isValid = typeof current === 'string';
|
|
778
|
+
langPathCache.set(langPath, isValid);
|
|
779
|
+
return isValid;
|
|
780
|
+
} catch (error) {
|
|
781
|
+
langPathCache.set(langPath, false);
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* 检查是否是动态内容
|
|
788
|
+
* @param {string} code - 代码内容
|
|
789
|
+
* @returns {boolean} 是否包含动态数据
|
|
790
|
+
*/
|
|
791
|
+
function isDynamicContent(code) {
|
|
792
|
+
// 更精确的动态内容检测
|
|
793
|
+
const dynamicPatterns = [
|
|
794
|
+
/\b(Math|Date|JSON|parseInt|parseFloat|Number|String|Array|Object)\b/,
|
|
795
|
+
/\b(data|item|index|info|common\.siteConfig)\b/,
|
|
796
|
+
/[+\-*/%]/,
|
|
797
|
+
/\brandom\b|\bfloor\b|\bceil\b|\bround\b/,
|
|
798
|
+
/\.\w+\(/, // 方法调用
|
|
799
|
+
/\$\{[^}]*[+\-*/%.][^}]*\}/ // 模板字符串中的计算
|
|
800
|
+
];
|
|
801
|
+
|
|
802
|
+
return dynamicPatterns.some(pattern => pattern.test(code));
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* 提取Pug节点中的common.lang引用
|
|
807
|
+
* @param {string} code - 代码内容
|
|
808
|
+
* @returns {string|null} 语言路径,如果不是common.lang引用则返回null
|
|
809
|
+
*/
|
|
810
|
+
function extractLangPath(code) {
|
|
811
|
+
// 匹配 common.lang.xxx
|
|
812
|
+
const langMatch = code.match(/common\.lang\.(.+)/);
|
|
813
|
+
if (langMatch) {
|
|
814
|
+
return langMatch[1];
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// 匹配 ${common.lang.xxx}
|
|
818
|
+
const templateLangMatch = code.match(/\$\{common\.lang\.([^}]+)\}/);
|
|
819
|
+
if (templateLangMatch) {
|
|
820
|
+
return templateLangMatch[1];
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// 匹配带反引号的模板字符串 `${common.lang.xxx}`
|
|
824
|
+
const backquoteLangMatch = code.match(/`.*\$\{common\.lang\.([^}]+)\}.*`/);
|
|
825
|
+
if (backquoteLangMatch) {
|
|
826
|
+
return backquoteLangMatch[1];
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* 使用 pug-parser 和 pug-walk 分析 pug 文件中的标签
|
|
834
|
+
* @param {string} pugContent - pug 文件内容
|
|
835
|
+
* @param {string} filename - 文件名
|
|
836
|
+
* @returns {Map} 行号到标签信息的映射
|
|
837
|
+
*/
|
|
838
|
+
function analyzeTagsFromPug(pugContent, filename) {
|
|
839
|
+
try {
|
|
840
|
+
const tokens = pugLexer(pugContent, { filename });
|
|
841
|
+
const ast = pugParser(tokens, { filename, src: pugContent });
|
|
842
|
+
|
|
843
|
+
const lineTagMap = new Map();
|
|
844
|
+
|
|
845
|
+
pugWalk(ast, function before(node) {
|
|
846
|
+
if (node.type === 'Tag' && node.line) {
|
|
847
|
+
const tagInfo = {
|
|
848
|
+
tagName: node.name,
|
|
849
|
+
line: node.line,
|
|
850
|
+
isEditable: false,
|
|
851
|
+
editableValue: "true",
|
|
852
|
+
textContent: '',
|
|
853
|
+
langPath: '',
|
|
854
|
+
hasStaticText: false,
|
|
855
|
+
hasLangText: false
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// 分析标签的子节点来确定文本内容
|
|
859
|
+
if (node.block && node.block.nodes) {
|
|
860
|
+
for (const child of node.block.nodes) {
|
|
861
|
+
// 处理Text节点(静态文本)
|
|
862
|
+
if (child.type === 'Text' && child.val && child.val.trim()) {
|
|
863
|
+
// 检查是否在同一行还有其他动态内容
|
|
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)
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
if (!hasOtherDynamicNodes) {
|
|
873
|
+
tagInfo.hasStaticText = true;
|
|
874
|
+
tagInfo.textContent = child.val.trim();
|
|
875
|
+
tagInfo.isEditable = true;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// 处理Code节点(插值表达式和代码块)
|
|
880
|
+
if (child.type === 'Code' && child.val) {
|
|
881
|
+
const langPath = extractLangPath(child.val);
|
|
882
|
+
if (langPath && !isDynamicContent(child.val)) {
|
|
883
|
+
if (checkLangPathExists(langPath)) {
|
|
884
|
+
tagInfo.hasLangText = true;
|
|
885
|
+
tagInfo.langPath = langPath;
|
|
886
|
+
tagInfo.isEditable = true;
|
|
887
|
+
// 转换为 [us][key1][key2] 格式
|
|
888
|
+
const pathParts = langPath.split('.');
|
|
889
|
+
tagInfo.editableValue = `us,${pathParts.join(',')}`;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// 检查节点本身是否有代码内容(对于!=语法)
|
|
897
|
+
if (node.code && node.code.val) {
|
|
898
|
+
const langPath = extractLangPath(node.code.val);
|
|
899
|
+
if (langPath && !isDynamicContent(node.code.val)) {
|
|
900
|
+
if (checkLangPathExists(langPath)) {
|
|
901
|
+
tagInfo.hasLangText = true;
|
|
902
|
+
tagInfo.langPath = langPath;
|
|
903
|
+
tagInfo.isEditable = true;
|
|
904
|
+
// 转换为 us,key1,key2 格式
|
|
905
|
+
const pathParts = langPath.split('.');
|
|
906
|
+
tagInfo.editableValue = `us,${pathParts.join(',')}`;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// 将标签信息添加到行映射中
|
|
912
|
+
if (!lineTagMap.has(node.line)) {
|
|
913
|
+
lineTagMap.set(node.line, []);
|
|
914
|
+
}
|
|
915
|
+
lineTagMap.get(node.line).push(tagInfo);
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
return lineTagMap;
|
|
920
|
+
} catch (error) {
|
|
921
|
+
console.error(`分析 pug 文件 ${filename} 时出错:`, error);
|
|
922
|
+
return new Map();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* 为 pug 文件内容添加调试属性
|
|
928
|
+
* @param {string} pugContent - pug 文件内容
|
|
929
|
+
* @param {string} relativePath - 文件相对路径
|
|
930
|
+
* @param {string} filename - 文件名
|
|
931
|
+
* @returns {string} 处理后的 pug 内容
|
|
932
|
+
*/
|
|
933
|
+
function addDebugAttributesToPug(pugContent, relativePath, filename) {
|
|
934
|
+
try {
|
|
935
|
+
const lines = pugContent.split('\n');
|
|
936
|
+
const lineTagMap = analyzeTagsFromPug(pugContent, filename);
|
|
937
|
+
|
|
938
|
+
// 定义不需要添加调试属性的标签
|
|
939
|
+
const excludeTags = new Set([
|
|
940
|
+
'head', 'title', 'meta', 'link', 'html', 'style', 'script',
|
|
941
|
+
'base', 'noscript', 'template'
|
|
942
|
+
]);
|
|
943
|
+
|
|
944
|
+
// 处理每一行
|
|
945
|
+
const processedLines = lines.map((line, index) => {
|
|
946
|
+
const lineNumber = index + 1;
|
|
947
|
+
const tagsInLine = lineTagMap.get(lineNumber);
|
|
948
|
+
|
|
949
|
+
if (!tagsInLine || tagsInLine.length === 0) {
|
|
950
|
+
return line;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// 过滤掉不需要处理的标签
|
|
954
|
+
const visibleTags = tagsInLine.filter(tag => !excludeTags.has(tag.tagName.toLowerCase()));
|
|
955
|
+
|
|
956
|
+
if (visibleTags.length === 0) {
|
|
957
|
+
return line;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// 对于每个标签,添加调试属性
|
|
961
|
+
let processedLine = line;
|
|
962
|
+
|
|
963
|
+
// 只处理第一个标签,避免处理插值表达式中的内容
|
|
964
|
+
const mainTag = visibleTags[0];
|
|
965
|
+
|
|
966
|
+
if (mainTag) {
|
|
967
|
+
// 构建调试属性
|
|
968
|
+
const debugAttrs = [
|
|
969
|
+
`data-debug-file="${relativePath}"`,
|
|
970
|
+
`data-debug-line="${lineNumber}"`
|
|
971
|
+
];
|
|
972
|
+
|
|
973
|
+
// 只有可编辑的元素才添加 data-debug-editable 属性
|
|
974
|
+
if (mainTag.isEditable) {
|
|
975
|
+
debugAttrs.push(`data-debug-editable="${mainTag.editableValue}"`);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const debugAttrsString = debugAttrs.join(', ');
|
|
979
|
+
|
|
980
|
+
// 简化的标签处理逻辑
|
|
981
|
+
const trimmedLine = line.trim();
|
|
982
|
+
|
|
983
|
+
// 处理隐式 div(.class 或 #id 语法)
|
|
984
|
+
if (trimmedLine.startsWith('.') || trimmedLine.startsWith('#')) {
|
|
985
|
+
const indent = line.match(/^\s*/)[0];
|
|
986
|
+
|
|
987
|
+
// 找到选择器结束的位置
|
|
988
|
+
let selectorEndIndex = 1;
|
|
989
|
+
while (selectorEndIndex < trimmedLine.length && /[\w-]/.test(trimmedLine[selectorEndIndex])) {
|
|
990
|
+
selectorEndIndex++;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// 检查是否已经有属性括号
|
|
994
|
+
const afterSelector = trimmedLine.substring(selectorEndIndex);
|
|
995
|
+
const parenIndex = afterSelector.indexOf('(');
|
|
996
|
+
|
|
997
|
+
if (parenIndex !== -1) {
|
|
998
|
+
// 已经有属性括号
|
|
999
|
+
const beforeParen = trimmedLine.substring(0, selectorEndIndex + parenIndex + 1);
|
|
1000
|
+
const afterFirstParen = trimmedLine.substring(selectorEndIndex + parenIndex + 1);
|
|
1001
|
+
const lastParenIndex = afterFirstParen.lastIndexOf(')');
|
|
1002
|
+
|
|
1003
|
+
if (lastParenIndex !== -1) {
|
|
1004
|
+
const existingAttrs = afterFirstParen.substring(0, lastParenIndex);
|
|
1005
|
+
const afterLastParen = afterFirstParen.substring(lastParenIndex);
|
|
1006
|
+
|
|
1007
|
+
const separator = existingAttrs.trim() ? ', ' : '';
|
|
1008
|
+
processedLine = `${indent}${beforeParen}${existingAttrs}${separator}${debugAttrsString}${afterLastParen}`;
|
|
1009
|
+
}
|
|
1010
|
+
} else {
|
|
1011
|
+
// 没有属性括号,在选择器后添加
|
|
1012
|
+
const selector = trimmedLine.substring(0, selectorEndIndex);
|
|
1013
|
+
const afterAttrs = trimmedLine.substring(selectorEndIndex);
|
|
1014
|
+
|
|
1015
|
+
processedLine = `${indent}${selector}(${debugAttrsString})${afterAttrs}`;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// 处理普通标签
|
|
1019
|
+
else if (trimmedLine.startsWith(mainTag.tagName)) {
|
|
1020
|
+
const indent = line.match(/^\s*/)[0];
|
|
1021
|
+
|
|
1022
|
+
// 找到标签名和修饰符的结束位置
|
|
1023
|
+
const tagEndIndex = findTagEnd(mainTag.tagName, trimmedLine);
|
|
1024
|
+
const afterTag = trimmedLine.substring(tagEndIndex);
|
|
1025
|
+
|
|
1026
|
+
if (afterTag.startsWith('(')) {
|
|
1027
|
+
// 已经有属性括号,需要找到正确的结束位置
|
|
1028
|
+
const parenEnd = findMatchingParen(trimmedLine, tagEndIndex);
|
|
1029
|
+
|
|
1030
|
+
if (parenEnd !== -1) {
|
|
1031
|
+
const tagWithModifiers = trimmedLine.substring(0, tagEndIndex);
|
|
1032
|
+
const existingAttrs = trimmedLine.substring(tagEndIndex + 1, parenEnd);
|
|
1033
|
+
const remaining = trimmedLine.substring(parenEnd + 1);
|
|
1034
|
+
|
|
1035
|
+
const separator = existingAttrs.trim() ? ', ' : '';
|
|
1036
|
+
processedLine = `${indent}${tagWithModifiers}(${existingAttrs}${separator}${debugAttrsString})${remaining}`;
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
// 没有属性括号,添加新的属性括号
|
|
1040
|
+
const tagWithModifiers = trimmedLine.substring(0, tagEndIndex);
|
|
1041
|
+
const remaining = trimmedLine.substring(tagEndIndex);
|
|
1042
|
+
|
|
1043
|
+
processedLine = `${indent}${tagWithModifiers}(${debugAttrsString})${remaining}`;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
return processedLine;
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
return processedLines.join('\n');
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
console.error(`处理 pug 文件 ${filename} 时出错:`, error);
|
|
1054
|
+
return pugContent;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* 找到标签名和修饰符的结束位置
|
|
1060
|
+
* @param {string} tagName - 标签名
|
|
1061
|
+
* @param {string} line - 行内容
|
|
1062
|
+
* @returns {number} 结束位置索引
|
|
1063
|
+
*/
|
|
1064
|
+
function findTagEnd(tagName, line) {
|
|
1065
|
+
let index = tagName.length;
|
|
1066
|
+
|
|
1067
|
+
// 跳过类名和ID修饰符
|
|
1068
|
+
while (index < line.length) {
|
|
1069
|
+
const char = line[index];
|
|
1070
|
+
if (char === '.' || char === '#') {
|
|
1071
|
+
index++;
|
|
1072
|
+
while (index < line.length && /[\w-]/.test(line[index])) {
|
|
1073
|
+
index++;
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
return index;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* 找到匹配的右括号
|
|
1085
|
+
* @param {string} str - 字符串
|
|
1086
|
+
* @param {number} start - 开始位置
|
|
1087
|
+
* @returns {number} 匹配括号的位置,-1表示未找到
|
|
1088
|
+
*/
|
|
1089
|
+
function findMatchingParen(str, start) {
|
|
1090
|
+
let depth = 0;
|
|
1091
|
+
let i = start;
|
|
1092
|
+
let inString = false;
|
|
1093
|
+
let stringChar = '';
|
|
1094
|
+
|
|
1095
|
+
while (i < str.length) {
|
|
1096
|
+
const char = str[i];
|
|
1097
|
+
|
|
1098
|
+
// 处理字符串内容,避免字符串中的括号影响匹配
|
|
1099
|
+
if (!inString && (char === '"' || char === "'" || char === '`')) {
|
|
1100
|
+
inString = true;
|
|
1101
|
+
stringChar = char;
|
|
1102
|
+
} else if (inString && char === stringChar && str[i-1] !== '\\') {
|
|
1103
|
+
inString = false;
|
|
1104
|
+
stringChar = '';
|
|
1105
|
+
} else if (!inString) {
|
|
1106
|
+
if (char === '(') {
|
|
1107
|
+
depth++;
|
|
1108
|
+
} else if (char === ')') {
|
|
1109
|
+
depth--;
|
|
1110
|
+
if (depth === 0) {
|
|
1111
|
+
return i;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
i++;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return -1; // 没有找到匹配的括号
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* 递归处理目录中的所有 pug 文件
|
|
1123
|
+
* @param {string} dir - 目录路径
|
|
1124
|
+
* @param {string} baseDir - 基础目录路径(用于计算相对路径)
|
|
1125
|
+
*/
|
|
1126
|
+
async function processPugFilesInDirectory(dir, baseDir) {
|
|
1127
|
+
try {
|
|
1128
|
+
const files = await fse.readdir(dir);
|
|
1129
|
+
|
|
1130
|
+
for (const file of files) {
|
|
1131
|
+
const fullPath = path.join(dir, file);
|
|
1132
|
+
const stats = await fse.stat(fullPath);
|
|
1133
|
+
|
|
1134
|
+
if (stats.isDirectory()) {
|
|
1135
|
+
// 递归处理子目录
|
|
1136
|
+
await processPugFilesInDirectory(fullPath, baseDir);
|
|
1137
|
+
} else if (file.endsWith('.pug')) {
|
|
1138
|
+
// 处理 pug 文件
|
|
1139
|
+
try {
|
|
1140
|
+
const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
|
|
1141
|
+
const pugContent = await fse.readFile(fullPath, 'utf8');
|
|
1142
|
+
|
|
1143
|
+
console.log(`正在处理: ${relativePath}`);
|
|
1144
|
+
|
|
1145
|
+
const processedContent = addDebugAttributesToPug(pugContent, relativePath, file);
|
|
1146
|
+
|
|
1147
|
+
// 写回处理后的内容
|
|
1148
|
+
await fse.writeFile(fullPath, processedContent, 'utf8');
|
|
1149
|
+
|
|
1150
|
+
console.log(`已处理: ${relativePath}`);
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
console.error(`处理文件 ${fullPath} 时出错:`, error);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
console.error(`处理目录 ${dir} 时出错:`, error);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// 开始处理调试目录中的所有 pug 文件
|
|
1162
|
+
console.log('开始为 pug 文件添加调试属性...');
|
|
1163
|
+
await processPugFilesInDirectory(targetDir, targetDir);
|
|
1164
|
+
|
|
1165
|
+
console.log('调试模板目录创建完成!');
|
|
1166
|
+
console.log(`调试模板位置: ${targetDir}`);
|
|
1167
|
+
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error('创建调试模板时出错:', error);
|
|
1170
|
+
throw error;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
package/lib/paths.js
CHANGED
|
@@ -7,6 +7,7 @@ const projectRoot = process.cwd();
|
|
|
7
7
|
|
|
8
8
|
// 定义基础路径
|
|
9
9
|
const templateRoot = path.join(projectRoot, "template");
|
|
10
|
+
const debugTemplateRoot = path.join(projectRoot, "template-debug");
|
|
10
11
|
|
|
11
12
|
// 辅助函数,用于生成文件URL
|
|
12
13
|
const fileUrl = (filePath) =>
|
|
@@ -18,12 +19,15 @@ export const paths = {
|
|
|
18
19
|
lib: __dirname,
|
|
19
20
|
config: fileUrl("config.js"),
|
|
20
21
|
getData: fileUrl("getData.js"),
|
|
22
|
+
languageData: fileUrl("languageData.js"),
|
|
21
23
|
pagesPugFn: fileUrl("pagesPugFn/index.js"),
|
|
22
24
|
router: fileUrl("router.js"),
|
|
23
25
|
|
|
24
26
|
// 模板相关路径
|
|
25
27
|
template: {
|
|
26
28
|
root: templateRoot,
|
|
29
|
+
debug: debugTemplateRoot,
|
|
30
|
+
debugPages: path.join(debugTemplateRoot, "pages"),
|
|
27
31
|
pages: path.join(templateRoot, "pages"),
|
|
28
32
|
static: path.join(templateRoot, "static"),
|
|
29
33
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pug-site-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"lang": "node index.js",
|
|
14
14
|
"imagemin": "node index.js",
|
|
15
15
|
"build": "npm run getData && npm run buildFn",
|
|
16
|
-
"update": "npm install pug-site-core@latest"
|
|
16
|
+
"update": "npm install pug-site-core@latest",
|
|
17
|
+
"debug": "node index.js"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [],
|
|
19
20
|
"author": "xy",
|
|
@@ -43,11 +44,14 @@
|
|
|
43
44
|
"lodash": "^4.17.21",
|
|
44
45
|
"nodemon": "^3.1.4",
|
|
45
46
|
"pug": "^3.0.3",
|
|
47
|
+
"pug-lexer": "^5.0.1",
|
|
48
|
+
"pug-parser": "^6.0.0",
|
|
49
|
+
"pug-walk": "^2.0.0",
|
|
46
50
|
"uglify-js": "^3.19.3",
|
|
47
51
|
"ws": "^8.18.0"
|
|
48
52
|
},
|
|
49
53
|
"license": "ISC",
|
|
50
|
-
"description": "
|
|
54
|
+
"description": "增加debug模板、添加abtest功能",
|
|
51
55
|
"files": [
|
|
52
56
|
"lib/",
|
|
53
57
|
"index.js"
|
|
@@ -55,4 +59,4 @@
|
|
|
55
59
|
"exports": {
|
|
56
60
|
".": "./index.js"
|
|
57
61
|
}
|
|
58
|
-
}
|
|
62
|
+
}
|