pug-site-core 3.0.17 → 3.0.19
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 +2 -2
- package/lib/generate.js +112 -35
- package/lib/translate.js +41 -2
- package/lib/utils.js +145 -82
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
fetchDataToJsonFile,
|
|
8
8
|
buildFn,
|
|
9
9
|
buildStatic,
|
|
10
|
-
createDebugTemplate
|
|
10
|
+
createDebugTemplate
|
|
11
11
|
} from "./lib/generate.js";
|
|
12
12
|
|
|
13
13
|
export const pugSiteCore = {
|
|
@@ -19,7 +19,7 @@ export const pugSiteCore = {
|
|
|
19
19
|
buildStatic,
|
|
20
20
|
translateLanguageData,
|
|
21
21
|
processImagemin,
|
|
22
|
-
createDebugTemplate
|
|
22
|
+
createDebugTemplate
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
let curCmd = process.env.npm_lifecycle_event;
|
package/lib/generate.js
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
obfuscateJavaScript,
|
|
13
13
|
addTemplateScopeIsolation,
|
|
14
14
|
getJsonData,
|
|
15
|
+
isLinux,
|
|
16
|
+
getLanguageListFromApi
|
|
15
17
|
} from "./utils.js";
|
|
16
18
|
import _ from "lodash";
|
|
17
19
|
import async from "async";
|
|
@@ -73,7 +75,7 @@ export async function compilePagesPugToFn(pugPath) {
|
|
|
73
75
|
basedir: paths.template.root,
|
|
74
76
|
compileDebug: true,
|
|
75
77
|
name: funName,
|
|
76
|
-
filters: getCompilePugFilter()
|
|
78
|
+
filters: getCompilePugFilter()
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
// 提取函数定义部分
|
|
@@ -154,7 +156,11 @@ export async function generateGetDataFn() {
|
|
|
154
156
|
await async.each(pagesPugFilePathArr, async (fileName) => {
|
|
155
157
|
const funName =
|
|
156
158
|
"get_" +
|
|
157
|
-
fileName
|
|
159
|
+
fileName
|
|
160
|
+
.split(pathSymbol)
|
|
161
|
+
.join("_")
|
|
162
|
+
.slice(0, -4)
|
|
163
|
+
.replaceAll(/[-]/g, "_") +
|
|
158
164
|
"_data";
|
|
159
165
|
|
|
160
166
|
// 使用正则表达式检查特定的数据获取函数是否存在
|
|
@@ -163,7 +169,10 @@ export async function generateGetDataFn() {
|
|
|
163
169
|
);
|
|
164
170
|
if (!dataFnRegex.test(getDataFile)) {
|
|
165
171
|
const templateFn = config.getDataFnTemplate.toString();
|
|
166
|
-
const dataFn = `export async ${templateFn.replace(
|
|
172
|
+
const dataFn = `export async ${templateFn.replace(
|
|
173
|
+
"template",
|
|
174
|
+
funName
|
|
175
|
+
)}`;
|
|
167
176
|
await fse.appendFile(getDataPath, dataFn + "\n");
|
|
168
177
|
}
|
|
169
178
|
});
|
|
@@ -212,15 +221,23 @@ export async function fetchDataToJsonFile(args) {
|
|
|
212
221
|
let checkData = (data, language, funName) => {
|
|
213
222
|
if (data === null || typeof data !== "object") {
|
|
214
223
|
return Promise.reject(
|
|
215
|
-
new Error(
|
|
224
|
+
new Error(
|
|
225
|
+
`${language} ${funName} 期望返回数组、对象类型返回: ${data}`
|
|
226
|
+
)
|
|
216
227
|
);
|
|
217
228
|
}
|
|
218
229
|
if (Array.isArray(data)) {
|
|
219
230
|
if (data.length === 0) {
|
|
220
|
-
return Promise.reject(
|
|
231
|
+
return Promise.reject(
|
|
232
|
+
new Error(`${language} ${funName} 数据为空数组`)
|
|
233
|
+
);
|
|
221
234
|
}
|
|
222
235
|
data.forEach((item, index) => {
|
|
223
|
-
if (
|
|
236
|
+
if (
|
|
237
|
+
item === null ||
|
|
238
|
+
typeof item !== "object" ||
|
|
239
|
+
Array.isArray(item)
|
|
240
|
+
) {
|
|
224
241
|
return Promise.reject(
|
|
225
242
|
new Error(
|
|
226
243
|
`${language} ${funName} 返回的数据不为对象数组类型返回: ${item} 下标为${index}`
|
|
@@ -250,7 +267,14 @@ export async function fetchDataToJsonFile(args) {
|
|
|
250
267
|
|
|
251
268
|
const pugFilePathList = await getPagesPugFilePathArr();
|
|
252
269
|
|
|
253
|
-
|
|
270
|
+
let languageList;
|
|
271
|
+
if (isLinux()) {
|
|
272
|
+
languageList = await getLanguageListFromApi();
|
|
273
|
+
} else {
|
|
274
|
+
languageList = config.languageList;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { customBuildData, fetchDataConcurrencyLimit } = config;
|
|
254
278
|
|
|
255
279
|
// 创建一个全局任务队列控制整体并发数
|
|
256
280
|
const queue = async.queue(async (task) => {
|
|
@@ -277,7 +301,9 @@ export async function fetchDataToJsonFile(args) {
|
|
|
277
301
|
allTasks.push(async () => {
|
|
278
302
|
console.log(language, commonFuncName, "开始写入json文件");
|
|
279
303
|
const commonData = await getData[commonFuncName](language);
|
|
280
|
-
let languageData = (await import(paths.languageData)).default[
|
|
304
|
+
let languageData = (await import(paths.languageData)).default[
|
|
305
|
+
language
|
|
306
|
+
];
|
|
281
307
|
commonData.lang = _.merge(commonData.lang, languageData);
|
|
282
308
|
await fse.outputJSON(
|
|
283
309
|
paths.resolveRoot("jsonData", language, "_common.json"),
|
|
@@ -299,7 +325,9 @@ export async function fetchDataToJsonFile(args) {
|
|
|
299
325
|
|
|
300
326
|
let dataFn = getData[obj.getDataFn];
|
|
301
327
|
if (!dataFn || typeof dataFn !== "function") {
|
|
302
|
-
return Promise.reject(
|
|
328
|
+
return Promise.reject(
|
|
329
|
+
new Error(obj.getDataFn + "获取数据函数不存在")
|
|
330
|
+
);
|
|
303
331
|
}
|
|
304
332
|
|
|
305
333
|
if (!isFillFun(obj.getDataFn)) {
|
|
@@ -347,7 +375,11 @@ export async function fetchDataToJsonFile(args) {
|
|
|
347
375
|
for (const fileName of pugFilePathList) {
|
|
348
376
|
let funName =
|
|
349
377
|
"get_" +
|
|
350
|
-
fileName
|
|
378
|
+
fileName
|
|
379
|
+
.split(pathSymbol)
|
|
380
|
+
.join("_")
|
|
381
|
+
.slice(0, -4)
|
|
382
|
+
.replaceAll(/[-]/g, "_") +
|
|
351
383
|
"_data";
|
|
352
384
|
|
|
353
385
|
let jsonFilePath = fileName.slice(0, -4).split(pathSymbol);
|
|
@@ -373,7 +405,11 @@ export async function fetchDataToJsonFile(args) {
|
|
|
373
405
|
item.page_name
|
|
374
406
|
);
|
|
375
407
|
} else {
|
|
376
|
-
console.warn(
|
|
408
|
+
console.warn(
|
|
409
|
+
"下标:",
|
|
410
|
+
index,
|
|
411
|
+
"无page_name属性,使用index作为文件名"
|
|
412
|
+
);
|
|
377
413
|
lastJsonFilePath =
|
|
378
414
|
paths.join(language, ...jsonFilePath) +
|
|
379
415
|
"_" +
|
|
@@ -406,7 +442,11 @@ export async function fetchDataToJsonFile(args) {
|
|
|
406
442
|
// 等待所有任务完成
|
|
407
443
|
return new Promise((resolve, reject) => {
|
|
408
444
|
queue.drain(() => {
|
|
409
|
-
console.log(
|
|
445
|
+
console.log(
|
|
446
|
+
"获取数据并写入完成花费:",
|
|
447
|
+
(Date.now() - starTime) / 1000,
|
|
448
|
+
"s"
|
|
449
|
+
);
|
|
410
450
|
resolve();
|
|
411
451
|
});
|
|
412
452
|
|
|
@@ -450,14 +490,22 @@ export async function buildFn() {
|
|
|
450
490
|
totalCommonData.langCommon = config.commonData;
|
|
451
491
|
let languageData = (await import(paths.languageData)).default;
|
|
452
492
|
await async.each(config.languageList, async (lang) => {
|
|
453
|
-
let commonData = await (
|
|
493
|
+
let commonData = await (
|
|
494
|
+
await import(paths.getData)
|
|
495
|
+
)["get_common_data"](lang);
|
|
454
496
|
commonData.lang = _.merge(commonData.lang, languageData[lang]);
|
|
455
497
|
totalCommonData[lang] = commonData;
|
|
456
498
|
});
|
|
457
499
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
500
|
+
if (config.siteConfig?.siteAbbr?.length > 0) {
|
|
501
|
+
const ABTestInfo = await fetchABTestInfo(config.siteConfig.siteAbbr);
|
|
502
|
+
if (ABTestInfo) {
|
|
503
|
+
totalCommonData.langCommon.ABTestInfo = ABTestInfo;
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
console.warn(
|
|
507
|
+
"config.siteConfig.siteAbbr 配置不存在,无法获取ABTestInfo 将不会开启abtest"
|
|
508
|
+
);
|
|
461
509
|
}
|
|
462
510
|
|
|
463
511
|
// await fse.copy(jsonDataPath, paths.resolveRoot(outputPath, "data"), {
|
|
@@ -478,11 +526,13 @@ export async function buildFn() {
|
|
|
478
526
|
}
|
|
479
527
|
if (config.buildStaticDirArr && config.buildStaticDirArr.length > 0) {
|
|
480
528
|
return !!config.buildStaticDirArr.find((item) => {
|
|
481
|
-
return src.startsWith(
|
|
529
|
+
return src.startsWith(
|
|
530
|
+
paths.resolveRoot(paths.template.static, item)
|
|
531
|
+
);
|
|
482
532
|
});
|
|
483
533
|
}
|
|
484
534
|
return true;
|
|
485
|
-
}
|
|
535
|
+
}
|
|
486
536
|
}
|
|
487
537
|
);
|
|
488
538
|
|
|
@@ -522,7 +572,7 @@ export async function buildStatic() {
|
|
|
522
572
|
useShortDoctype: true,
|
|
523
573
|
removeEmptyAttributes: true,
|
|
524
574
|
removeOptionalTags: true,
|
|
525
|
-
caseSensitive: false
|
|
575
|
+
caseSensitive: false
|
|
526
576
|
};
|
|
527
577
|
|
|
528
578
|
/**
|
|
@@ -585,7 +635,7 @@ export async function buildStatic() {
|
|
|
585
635
|
return pagesPugToFn[funName]({
|
|
586
636
|
data,
|
|
587
637
|
_pagePath: pagePath,
|
|
588
|
-
common: commonData
|
|
638
|
+
common: commonData
|
|
589
639
|
});
|
|
590
640
|
}
|
|
591
641
|
|
|
@@ -614,7 +664,9 @@ export async function buildStatic() {
|
|
|
614
664
|
await async.eachLimit(data, 128, async (dataItem, index) => {
|
|
615
665
|
const fileName = dataItem[property];
|
|
616
666
|
if (!fileName) {
|
|
617
|
-
throw new Error(
|
|
667
|
+
throw new Error(
|
|
668
|
+
`数据项索引 ${index} 中缺少属性 ${property} 或值为空`
|
|
669
|
+
);
|
|
618
670
|
}
|
|
619
671
|
|
|
620
672
|
const htmlPath = buildHtmlPath(lang, obj.outPutHtmlPath, fileName);
|
|
@@ -630,7 +682,13 @@ export async function buildStatic() {
|
|
|
630
682
|
});
|
|
631
683
|
} else {
|
|
632
684
|
const htmlPath = buildHtmlPath(lang, obj.outPutHtmlPath);
|
|
633
|
-
const html = generateHtml(
|
|
685
|
+
const html = generateHtml(
|
|
686
|
+
pagesPugToFn,
|
|
687
|
+
funName,
|
|
688
|
+
data,
|
|
689
|
+
pagePath,
|
|
690
|
+
commonData
|
|
691
|
+
);
|
|
634
692
|
const compressedHtml = await compressHtml(html, htmlPath);
|
|
635
693
|
await fse.outputFile(htmlPath, compressedHtml);
|
|
636
694
|
}
|
|
@@ -657,7 +715,13 @@ export async function buildStatic() {
|
|
|
657
715
|
) {
|
|
658
716
|
try {
|
|
659
717
|
const htmlPath = buildHtmlPath(lang, obj.outPutHtmlPath);
|
|
660
|
-
const html = generateHtml(
|
|
718
|
+
const html = generateHtml(
|
|
719
|
+
pagesPugToFn,
|
|
720
|
+
funName,
|
|
721
|
+
data,
|
|
722
|
+
pagePath,
|
|
723
|
+
commonData
|
|
724
|
+
);
|
|
661
725
|
const compressedHtml = await compressHtml(html, htmlPath);
|
|
662
726
|
await fse.outputFile(htmlPath, compressedHtml);
|
|
663
727
|
} catch (error) {
|
|
@@ -698,7 +762,7 @@ export async function buildStatic() {
|
|
|
698
762
|
jsonPath = paths.join(lang, jsonPath);
|
|
699
763
|
let data = await getJsonData(jsonPath);
|
|
700
764
|
return data;
|
|
701
|
-
}
|
|
765
|
+
}
|
|
702
766
|
});
|
|
703
767
|
const data = await getDataFn(lang);
|
|
704
768
|
|
|
@@ -734,7 +798,9 @@ export async function buildStatic() {
|
|
|
734
798
|
}
|
|
735
799
|
} catch (error) {
|
|
736
800
|
console.error(
|
|
737
|
-
`处理自定义HTML构建失败 [lang: ${lang}, config: ${JSON.stringify(
|
|
801
|
+
`处理自定义HTML构建失败 [lang: ${lang}, config: ${JSON.stringify(
|
|
802
|
+
obj
|
|
803
|
+
)}]:`,
|
|
738
804
|
error
|
|
739
805
|
);
|
|
740
806
|
throw error;
|
|
@@ -826,11 +892,13 @@ export async function buildStatic() {
|
|
|
826
892
|
}
|
|
827
893
|
if (config.buildStaticDirArr && config.buildStaticDirArr.length > 0) {
|
|
828
894
|
return !!config.buildStaticDirArr.find((item) => {
|
|
829
|
-
return src.startsWith(
|
|
895
|
+
return src.startsWith(
|
|
896
|
+
paths.resolveRoot(paths.template.static, item)
|
|
897
|
+
);
|
|
830
898
|
});
|
|
831
899
|
}
|
|
832
900
|
return true;
|
|
833
|
-
}
|
|
901
|
+
}
|
|
834
902
|
}
|
|
835
903
|
);
|
|
836
904
|
|
|
@@ -992,7 +1060,7 @@ export async function createDebugTemplate() {
|
|
|
992
1060
|
/[+\-*/%]/,
|
|
993
1061
|
/\brandom\b|\bfloor\b|\bceil\b|\bround\b/,
|
|
994
1062
|
/\.\w+\(/, // 方法调用
|
|
995
|
-
/\$\{[^}]*[+\-*/%.][^}]*\}
|
|
1063
|
+
/\$\{[^}]*[+\-*/%.][^}]*\}/ // 模板字符串中的计算
|
|
996
1064
|
];
|
|
997
1065
|
|
|
998
1066
|
return dynamicPatterns.some((pattern) => pattern.test(code));
|
|
@@ -1017,7 +1085,9 @@ export async function createDebugTemplate() {
|
|
|
1017
1085
|
}
|
|
1018
1086
|
|
|
1019
1087
|
// 匹配带反引号的模板字符串 `${common.lang.xxx}`
|
|
1020
|
-
const backquoteLangMatch = code.match(
|
|
1088
|
+
const backquoteLangMatch = code.match(
|
|
1089
|
+
/`.*\$\{common\.lang\.([^}]+)\}.*`/
|
|
1090
|
+
);
|
|
1021
1091
|
if (backquoteLangMatch) {
|
|
1022
1092
|
return backquoteLangMatch[1];
|
|
1023
1093
|
}
|
|
@@ -1048,7 +1118,7 @@ export async function createDebugTemplate() {
|
|
|
1048
1118
|
textContent: "",
|
|
1049
1119
|
langPath: "",
|
|
1050
1120
|
hasStaticText: false,
|
|
1051
|
-
hasLangText: false
|
|
1121
|
+
hasLangText: false
|
|
1052
1122
|
};
|
|
1053
1123
|
|
|
1054
1124
|
// 分析标签的子节点来确定文本内容
|
|
@@ -1144,7 +1214,7 @@ export async function createDebugTemplate() {
|
|
|
1144
1214
|
"script",
|
|
1145
1215
|
"base",
|
|
1146
1216
|
"noscript",
|
|
1147
|
-
"template"
|
|
1217
|
+
"template"
|
|
1148
1218
|
]);
|
|
1149
1219
|
|
|
1150
1220
|
// 处理每一行
|
|
@@ -1175,7 +1245,7 @@ export async function createDebugTemplate() {
|
|
|
1175
1245
|
// 构建调试属性
|
|
1176
1246
|
const debugAttrs = [
|
|
1177
1247
|
`data-debug-file="${relativePath}"`,
|
|
1178
|
-
`data-debug-line="${lineNumber}"
|
|
1248
|
+
`data-debug-line="${lineNumber}"`
|
|
1179
1249
|
];
|
|
1180
1250
|
|
|
1181
1251
|
// 只有可编辑的元素才添加 data-debug-editable 属性
|
|
@@ -1217,8 +1287,12 @@ export async function createDebugTemplate() {
|
|
|
1217
1287
|
const lastParenIndex = afterFirstParen.lastIndexOf(")");
|
|
1218
1288
|
|
|
1219
1289
|
if (lastParenIndex !== -1) {
|
|
1220
|
-
const existingAttrs = afterFirstParen.substring(
|
|
1221
|
-
|
|
1290
|
+
const existingAttrs = afterFirstParen.substring(
|
|
1291
|
+
0,
|
|
1292
|
+
lastParenIndex
|
|
1293
|
+
);
|
|
1294
|
+
const afterLastParen =
|
|
1295
|
+
afterFirstParen.substring(lastParenIndex);
|
|
1222
1296
|
|
|
1223
1297
|
const separator = existingAttrs.trim() ? ", " : "";
|
|
1224
1298
|
processedLine = `${indent}${beforeParen}${existingAttrs}${separator}${debugAttrsString}${afterLastParen}`;
|
|
@@ -1244,7 +1318,10 @@ export async function createDebugTemplate() {
|
|
|
1244
1318
|
const parenEnd = findMatchingParen(trimmedLine, tagEndIndex);
|
|
1245
1319
|
|
|
1246
1320
|
if (parenEnd !== -1) {
|
|
1247
|
-
const tagWithModifiers = trimmedLine.substring(
|
|
1321
|
+
const tagWithModifiers = trimmedLine.substring(
|
|
1322
|
+
0,
|
|
1323
|
+
tagEndIndex
|
|
1324
|
+
);
|
|
1248
1325
|
const existingAttrs = trimmedLine.substring(
|
|
1249
1326
|
tagEndIndex + 1,
|
|
1250
1327
|
parenEnd
|
package/lib/translate.js
CHANGED
|
@@ -19,9 +19,15 @@ let translate = new v2.Translate({ projectId, key });
|
|
|
19
19
|
|
|
20
20
|
let orginLang = "us";
|
|
21
21
|
|
|
22
|
+
// 命令行参数说明:
|
|
23
|
+
// k=key1,key2 - 指定要翻译的键
|
|
24
|
+
// c=us,kr,jp - 指定目标国家/语言
|
|
25
|
+
// all=true - 使用 allLang 中的所有语言进行翻译
|
|
22
26
|
let args = process.argv.slice(2);
|
|
23
27
|
let filParms = [];
|
|
24
28
|
let filCountry = [];
|
|
29
|
+
let useAllLang = false;
|
|
30
|
+
|
|
25
31
|
args.forEach((item) => {
|
|
26
32
|
const [key, value] = item.split("=");
|
|
27
33
|
if (value) {
|
|
@@ -32,9 +38,42 @@ args.forEach((item) => {
|
|
|
32
38
|
filCountry = value.split(",");
|
|
33
39
|
}
|
|
34
40
|
}
|
|
41
|
+
if (key === "all" && value === "true") {
|
|
42
|
+
useAllLang = true;
|
|
43
|
+
}
|
|
35
44
|
});
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
const allLang = [
|
|
47
|
+
"us",
|
|
48
|
+
"us2",
|
|
49
|
+
"ar",
|
|
50
|
+
"br",
|
|
51
|
+
"de",
|
|
52
|
+
"es",
|
|
53
|
+
"fr",
|
|
54
|
+
"id",
|
|
55
|
+
"it",
|
|
56
|
+
"jp",
|
|
57
|
+
"kr",
|
|
58
|
+
"nl",
|
|
59
|
+
"pl",
|
|
60
|
+
"th",
|
|
61
|
+
"tr",
|
|
62
|
+
"tw",
|
|
63
|
+
"vn",
|
|
64
|
+
"ru",
|
|
65
|
+
"pt",
|
|
66
|
+
"sv",
|
|
67
|
+
"fi",
|
|
68
|
+
"ms",
|
|
69
|
+
"in"
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
let targetLangList = useAllLang
|
|
73
|
+
? allLang
|
|
74
|
+
: filCountry.length > 0
|
|
75
|
+
? filCountry
|
|
76
|
+
: config.languageList;
|
|
38
77
|
|
|
39
78
|
//国家映射到语言
|
|
40
79
|
const countryLanguageMap = {
|
|
@@ -48,7 +87,7 @@ const countryLanguageMap = {
|
|
|
48
87
|
tw: "zh-TW",
|
|
49
88
|
gb: "en",
|
|
50
89
|
br: "pt",
|
|
51
|
-
in: "hi"
|
|
90
|
+
in: "hi"
|
|
52
91
|
};
|
|
53
92
|
|
|
54
93
|
async function translateStr(str, targetLanguage) {
|
package/lib/utils.js
CHANGED
|
@@ -5,12 +5,19 @@ import { paths } from "./paths.js";
|
|
|
5
5
|
import detectPort from "detect-port";
|
|
6
6
|
import axios from "axios";
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
const { config } = await import(paths.config);
|
|
10
9
|
|
|
11
10
|
// 根据操作系统设置路径分隔符
|
|
12
11
|
export const pathSymbol = process.platform.startsWith("win") ? "\\" : "/";
|
|
13
12
|
|
|
13
|
+
/**
|
|
14
|
+
* 判断当前环境是否为Linux系统
|
|
15
|
+
* @returns {boolean} 如果是Linux系统返回true,否则返回false
|
|
16
|
+
*/
|
|
17
|
+
export function isLinux() {
|
|
18
|
+
return process.platform === "linux";
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* 获取pages目录下所有pug文件的路径数组
|
|
16
23
|
* @returns {Promise<string[]>} 返回pug文件路径数组
|
|
@@ -18,7 +25,7 @@ export const pathSymbol = process.platform.startsWith("win") ? "\\" : "/";
|
|
|
18
25
|
export async function getPagesPugFilePathArr() {
|
|
19
26
|
let pagesPugFilePathArr = (
|
|
20
27
|
await fse.readdir(paths.template.pages, {
|
|
21
|
-
recursive: true
|
|
28
|
+
recursive: true
|
|
22
29
|
})
|
|
23
30
|
).filter((fileName) => fileName.endsWith(".pug"));
|
|
24
31
|
|
|
@@ -262,8 +269,8 @@ export async function obfuscateJavaScript(
|
|
|
262
269
|
unused: true,
|
|
263
270
|
if_return: true,
|
|
264
271
|
join_vars: true,
|
|
265
|
-
drop_console: true
|
|
266
|
-
}
|
|
272
|
+
drop_console: true
|
|
273
|
+
}
|
|
267
274
|
};
|
|
268
275
|
|
|
269
276
|
// 合并配置选项
|
|
@@ -393,14 +400,21 @@ export async function getJsonData(jsonDataPath) {
|
|
|
393
400
|
* @param {boolean} options.preserveComments - 是否保留注释,默认为true
|
|
394
401
|
* @returns {string} 处理后的HTML字符串
|
|
395
402
|
*/
|
|
396
|
-
export function addTemplateScopeIsolation(
|
|
403
|
+
export function addTemplateScopeIsolation(
|
|
404
|
+
htmlString,
|
|
405
|
+
scopePrefix = "xy",
|
|
406
|
+
options = {}
|
|
407
|
+
) {
|
|
397
408
|
// 参数验证
|
|
398
409
|
if (!htmlString || typeof htmlString !== "string") {
|
|
399
410
|
return htmlString;
|
|
400
411
|
}
|
|
401
412
|
|
|
402
413
|
// 快速检查,避免不必要的处理
|
|
403
|
-
if (
|
|
414
|
+
if (
|
|
415
|
+
!htmlString.includes("<template>") ||
|
|
416
|
+
!htmlString.includes("</template>")
|
|
417
|
+
) {
|
|
404
418
|
return htmlString;
|
|
405
419
|
}
|
|
406
420
|
|
|
@@ -424,15 +438,15 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
424
438
|
if (!selector || !selector.trim()) {
|
|
425
439
|
return selector;
|
|
426
440
|
}
|
|
427
|
-
|
|
441
|
+
|
|
428
442
|
selector = selector.trim();
|
|
429
|
-
|
|
443
|
+
|
|
430
444
|
// 处理伪类和伪元素(更完善的正则)
|
|
431
445
|
const pseudoMatch = selector.match(/^([^:]+?)(::?[^[]+)?$/);
|
|
432
446
|
if (pseudoMatch) {
|
|
433
|
-
const [, base, pseudo =
|
|
447
|
+
const [, base, pseudo = ""] = pseudoMatch;
|
|
434
448
|
// 处理属性选择器
|
|
435
|
-
if (base.includes(
|
|
449
|
+
if (base.includes("[")) {
|
|
436
450
|
const attrMatch = base.match(/^([^[]+)(\[.+\])$/);
|
|
437
451
|
if (attrMatch) {
|
|
438
452
|
return `${attrMatch[1]}[data-${scopeId}]${attrMatch[2]}${pseudo}`;
|
|
@@ -440,7 +454,7 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
440
454
|
}
|
|
441
455
|
return `${base}[data-${scopeId}]${pseudo}`;
|
|
442
456
|
}
|
|
443
|
-
|
|
457
|
+
|
|
444
458
|
return `${selector}[data-${scopeId}]`;
|
|
445
459
|
}
|
|
446
460
|
|
|
@@ -448,52 +462,57 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
448
462
|
function parseHTML(html, depth = 0) {
|
|
449
463
|
// 检查递归深度
|
|
450
464
|
if (depth >= config.maxDepth) {
|
|
451
|
-
console.warn(
|
|
465
|
+
console.warn(
|
|
466
|
+
`Maximum recursion depth (${config.maxDepth}) reached in template processing`
|
|
467
|
+
);
|
|
452
468
|
return html;
|
|
453
469
|
}
|
|
454
|
-
|
|
470
|
+
|
|
455
471
|
// 使用更精确的模板匹配(避免贪婪匹配问题)
|
|
456
472
|
const templates = [];
|
|
457
473
|
let tempHtml = html;
|
|
458
474
|
let placeholder = 0;
|
|
459
|
-
|
|
475
|
+
|
|
460
476
|
// 先收集所有template标签的位置和内容
|
|
461
477
|
const templateRegex = /<template([^>]*)>([\s\S]*?)<\/template>/gi;
|
|
462
478
|
let match;
|
|
463
|
-
|
|
479
|
+
|
|
464
480
|
while ((match = templateRegex.exec(html)) !== null) {
|
|
465
481
|
templates.push({
|
|
466
482
|
full: match[0],
|
|
467
|
-
attributes: match[1] ||
|
|
468
|
-
content: match[2] ||
|
|
483
|
+
attributes: match[1] || "",
|
|
484
|
+
content: match[2] || "",
|
|
469
485
|
start: match.index,
|
|
470
486
|
placeholder: `__TEMPLATE_PLACEHOLDER_${placeholder++}__`
|
|
471
487
|
});
|
|
472
488
|
}
|
|
473
|
-
|
|
489
|
+
|
|
474
490
|
// 从后往前替换,避免索引偏移问题
|
|
475
491
|
for (let i = templates.length - 1; i >= 0; i--) {
|
|
476
492
|
const template = templates[i];
|
|
477
493
|
const scopeId = generateScopeId(scopePrefix);
|
|
478
|
-
|
|
494
|
+
|
|
479
495
|
// 处理内容
|
|
480
496
|
const processedContent = addScopeToHTML(template.content, scopeId);
|
|
481
|
-
|
|
497
|
+
|
|
482
498
|
// 创建新的div标签,保留原有属性
|
|
483
|
-
const attributesStr = template.attributes
|
|
499
|
+
const attributesStr = template.attributes
|
|
500
|
+
? ` ${template.attributes.trim()}`
|
|
501
|
+
: "";
|
|
484
502
|
const newDiv = `<div data-${scopeId}=""${attributesStr}>${processedContent}</div>`;
|
|
485
|
-
|
|
503
|
+
|
|
486
504
|
// 替换原始template
|
|
487
|
-
tempHtml =
|
|
488
|
-
|
|
489
|
-
|
|
505
|
+
tempHtml =
|
|
506
|
+
tempHtml.substring(0, template.start) +
|
|
507
|
+
newDiv +
|
|
508
|
+
tempHtml.substring(template.start + template.full.length);
|
|
490
509
|
}
|
|
491
|
-
|
|
510
|
+
|
|
492
511
|
// 检查是否还有嵌套的template需要处理
|
|
493
|
-
if (tempHtml.includes(
|
|
512
|
+
if (tempHtml.includes("<template>") && depth < config.maxDepth - 1) {
|
|
494
513
|
return parseHTML(tempHtml, depth + 1);
|
|
495
514
|
}
|
|
496
|
-
|
|
515
|
+
|
|
497
516
|
return tempHtml;
|
|
498
517
|
}
|
|
499
518
|
|
|
@@ -501,41 +520,45 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
501
520
|
function addScopeToHTML(html, scopeId) {
|
|
502
521
|
// 处理HTML标签(改进的正则,支持自闭合标签)
|
|
503
522
|
let processedHtml = html.replace(
|
|
504
|
-
/<(\w+)([^>]*?)(\/?)>/g,
|
|
523
|
+
/<(\w+)([^>]*?)(\/?)>/g,
|
|
505
524
|
(match, tagName, attributes, selfClosing) => {
|
|
506
525
|
// 跳过已经有作用域属性的标签
|
|
507
|
-
if (
|
|
508
|
-
|
|
509
|
-
|
|
526
|
+
if (
|
|
527
|
+
attributes.includes("data-v-") ||
|
|
528
|
+
attributes.includes(`data-${scopeId}`) ||
|
|
529
|
+
attributes.includes(`data-${scopePrefix}-`)
|
|
530
|
+
) {
|
|
510
531
|
return match;
|
|
511
532
|
}
|
|
512
|
-
|
|
533
|
+
|
|
513
534
|
// 处理特殊标签(不需要作用域的)
|
|
514
|
-
const skipTags = [
|
|
535
|
+
const skipTags = ["script", "style", "template"];
|
|
515
536
|
if (skipTags.includes(tagName.toLowerCase())) {
|
|
516
537
|
return match;
|
|
517
538
|
}
|
|
518
|
-
|
|
539
|
+
|
|
519
540
|
// 为标签添加作用域属性
|
|
520
541
|
return `<${tagName} data-${scopeId}=""${attributes}${selfClosing}>`;
|
|
521
542
|
}
|
|
522
543
|
);
|
|
523
|
-
|
|
544
|
+
|
|
524
545
|
// 处理style标签中的CSS
|
|
525
546
|
processedHtml = processedHtml.replace(
|
|
526
|
-
/<style([^>]*?)>([\s\S]*?)<\/style>/gi,
|
|
547
|
+
/<style([^>]*?)>([\s\S]*?)<\/style>/gi,
|
|
527
548
|
(match, styleAttrs, cssContent) => {
|
|
528
549
|
// 跳过已处理的CSS
|
|
529
|
-
if (
|
|
530
|
-
|
|
550
|
+
if (
|
|
551
|
+
cssContent.includes(`[data-${scopeId}]`) ||
|
|
552
|
+
cssContent.includes(`[data-${scopePrefix}-`)
|
|
553
|
+
) {
|
|
531
554
|
return match;
|
|
532
555
|
}
|
|
533
|
-
|
|
556
|
+
|
|
534
557
|
const scopedCSS = addScopeToCSS(cssContent, scopeId);
|
|
535
558
|
return `<style${styleAttrs}>${scopedCSS}</style>`;
|
|
536
559
|
}
|
|
537
560
|
);
|
|
538
|
-
|
|
561
|
+
|
|
539
562
|
return processedHtml;
|
|
540
563
|
}
|
|
541
564
|
|
|
@@ -544,7 +567,7 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
544
567
|
// 保存注释
|
|
545
568
|
const comments = [];
|
|
546
569
|
let commentIndex = 0;
|
|
547
|
-
|
|
570
|
+
|
|
548
571
|
if (config.preserveComments) {
|
|
549
572
|
cssContent = cssContent.replace(/\/\*[\s\S]*?\*\//g, (match) => {
|
|
550
573
|
const placeholder = `__CSS_COMMENT_${commentIndex++}__`;
|
|
@@ -552,86 +575,91 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
552
575
|
return placeholder;
|
|
553
576
|
});
|
|
554
577
|
}
|
|
555
|
-
|
|
578
|
+
|
|
556
579
|
// 改进的CSS规则处理
|
|
557
580
|
function processCSS(css, level = 0) {
|
|
558
581
|
if (level > 5) return css; // 防止过深的嵌套
|
|
559
|
-
|
|
582
|
+
|
|
560
583
|
// 匹配CSS规则块
|
|
561
584
|
const ruleRegex = /([^{}]+)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g;
|
|
562
|
-
|
|
585
|
+
|
|
563
586
|
return css.replace(ruleRegex, (match, selectors, content) => {
|
|
564
587
|
const trimmedSelectors = selectors.trim();
|
|
565
|
-
|
|
588
|
+
|
|
566
589
|
// 处理@规则
|
|
567
|
-
if (trimmedSelectors.startsWith(
|
|
590
|
+
if (trimmedSelectors.startsWith("@")) {
|
|
568
591
|
// @keyframes不需要作用域
|
|
569
|
-
if (trimmedSelectors.startsWith(
|
|
592
|
+
if (trimmedSelectors.startsWith("@keyframes")) {
|
|
570
593
|
return match;
|
|
571
594
|
}
|
|
572
|
-
|
|
595
|
+
|
|
573
596
|
// @media, @supports等需要递归处理内部规则
|
|
574
|
-
if (content.includes(
|
|
597
|
+
if (content.includes("{")) {
|
|
575
598
|
const processedContent = processCSS(content, level + 1);
|
|
576
599
|
return `${selectors} {${processedContent}}`;
|
|
577
600
|
}
|
|
578
|
-
|
|
601
|
+
|
|
579
602
|
return match;
|
|
580
603
|
}
|
|
581
|
-
|
|
604
|
+
|
|
582
605
|
// 处理普通选择器
|
|
583
606
|
const processedSelectors = trimmedSelectors
|
|
584
|
-
.split(
|
|
585
|
-
.map(selector => {
|
|
607
|
+
.split(",")
|
|
608
|
+
.map((selector) => {
|
|
586
609
|
selector = selector.trim();
|
|
587
|
-
|
|
610
|
+
|
|
588
611
|
// 跳过特殊情况
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
612
|
+
if (
|
|
613
|
+
!selector ||
|
|
614
|
+
selector.startsWith("@") ||
|
|
615
|
+
/^(from|to|\d+%)$/.test(selector) ||
|
|
616
|
+
selector.includes(`[data-${scopeId}]`)
|
|
617
|
+
) {
|
|
593
618
|
return selector;
|
|
594
619
|
}
|
|
595
|
-
|
|
620
|
+
|
|
596
621
|
// 清理现有的作用域属性
|
|
597
|
-
selector = selector.replace(/\[data-[a-z0-9-]+\]/gi,
|
|
598
|
-
|
|
622
|
+
selector = selector.replace(/\[data-[a-z0-9-]+\]/gi, "").trim();
|
|
623
|
+
|
|
599
624
|
// 处理复杂选择器
|
|
600
625
|
const selectorParts = selector.split(/\s+/);
|
|
601
|
-
|
|
626
|
+
|
|
602
627
|
// 处理第一个选择器部分
|
|
603
628
|
if (selectorParts.length > 0) {
|
|
604
629
|
// 处理组合选择器(如 .class1.class2)
|
|
605
630
|
const firstPart = selectorParts[0];
|
|
606
631
|
const combinedSelectors = firstPart.split(/(?=[.#[])/);
|
|
607
|
-
|
|
632
|
+
|
|
608
633
|
if (combinedSelectors.length > 1) {
|
|
609
634
|
// 在第一个实际选择器后添加作用域
|
|
610
|
-
combinedSelectors[0] = addScopeToSimpleSelector(
|
|
611
|
-
|
|
635
|
+
combinedSelectors[0] = addScopeToSimpleSelector(
|
|
636
|
+
combinedSelectors[0],
|
|
637
|
+
scopeId
|
|
638
|
+
);
|
|
639
|
+
selectorParts[0] = combinedSelectors.join("");
|
|
612
640
|
} else {
|
|
613
641
|
selectorParts[0] = addScopeToSimpleSelector(firstPart, scopeId);
|
|
614
642
|
}
|
|
615
643
|
}
|
|
616
|
-
|
|
617
|
-
return selectorParts.join(
|
|
644
|
+
|
|
645
|
+
return selectorParts.join(" ");
|
|
618
646
|
})
|
|
619
|
-
.join(
|
|
620
|
-
|
|
647
|
+
.join(", ");
|
|
648
|
+
|
|
621
649
|
return `${processedSelectors} {${content}}`;
|
|
622
650
|
});
|
|
623
651
|
}
|
|
624
|
-
|
|
652
|
+
|
|
625
653
|
// 处理CSS
|
|
626
654
|
let processedCSS = processCSS(cssContent);
|
|
627
|
-
|
|
655
|
+
|
|
628
656
|
// 恢复注释
|
|
629
657
|
if (config.preserveComments) {
|
|
630
658
|
comments.forEach(({ placeholder, content }) => {
|
|
631
659
|
processedCSS = processedCSS.replace(placeholder, content);
|
|
632
660
|
});
|
|
633
661
|
}
|
|
634
|
-
|
|
662
|
+
|
|
635
663
|
return processedCSS;
|
|
636
664
|
}
|
|
637
665
|
|
|
@@ -639,7 +667,7 @@ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", option
|
|
|
639
667
|
try {
|
|
640
668
|
return parseHTML(htmlString);
|
|
641
669
|
} catch (error) {
|
|
642
|
-
console.error(
|
|
670
|
+
console.error("Error in addTemplateScopeIsolation:", error);
|
|
643
671
|
return htmlString; // 出错时返回原始内容
|
|
644
672
|
}
|
|
645
673
|
}
|
|
@@ -653,14 +681,14 @@ export async function getFilePathByFunctionName(functionName) {
|
|
|
653
681
|
try {
|
|
654
682
|
// 获取所有pug文件路径
|
|
655
683
|
const pagesPugFilePathArr = await getPagesPugFilePathArr();
|
|
656
|
-
|
|
684
|
+
|
|
657
685
|
// 清理函数名,移除get_前缀和_data后缀(如果存在)
|
|
658
686
|
let cleanFunctionName = functionName;
|
|
659
687
|
if (functionName.startsWith("get_") && functionName.endsWith("_data")) {
|
|
660
688
|
// 数据获取函数名格式:get_xxx_data
|
|
661
689
|
cleanFunctionName = functionName.slice(4, -5); // 移除 "get_" 和 "_data"
|
|
662
690
|
}
|
|
663
|
-
|
|
691
|
+
|
|
664
692
|
// 遍历所有pug文件,找到匹配的文件
|
|
665
693
|
for (const fileName of pagesPugFilePathArr) {
|
|
666
694
|
// 根据fileName生成函数名(与generate.js中的逻辑保持一致)
|
|
@@ -669,13 +697,16 @@ export async function getFilePathByFunctionName(functionName) {
|
|
|
669
697
|
.join("_")
|
|
670
698
|
.slice(0, -4) // 移除.pug扩展名
|
|
671
699
|
.replaceAll(/[-]/g, "_");
|
|
672
|
-
|
|
700
|
+
|
|
673
701
|
// 检查是否匹配
|
|
674
|
-
if (
|
|
702
|
+
if (
|
|
703
|
+
generatedFunctionName === cleanFunctionName ||
|
|
704
|
+
generatedFunctionName === functionName
|
|
705
|
+
) {
|
|
675
706
|
return fileName;
|
|
676
707
|
}
|
|
677
708
|
}
|
|
678
|
-
|
|
709
|
+
|
|
679
710
|
return null;
|
|
680
711
|
} catch (error) {
|
|
681
712
|
console.error("根据函数名查找文件路径失败:", error);
|
|
@@ -691,11 +722,43 @@ export async function getFilePathByFunctionName(functionName) {
|
|
|
691
722
|
export async function fetchABTestInfo(siteAbbr) {
|
|
692
723
|
try {
|
|
693
724
|
const res = await axios.get("http://new.sp.com/open-api/abtest-info", {
|
|
694
|
-
params: { site: siteAbbr }
|
|
725
|
+
params: { site: siteAbbr }
|
|
695
726
|
});
|
|
696
|
-
return
|
|
727
|
+
return res && res.data && res.data.result ? res.data.result : null;
|
|
697
728
|
} catch (error) {
|
|
698
729
|
console.error("获取ABTestInfo失败:", error);
|
|
699
730
|
return null;
|
|
700
731
|
}
|
|
701
|
-
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* 从接口获取语言列表
|
|
736
|
+
* @returns {Promise<string[]>} 返回语言列表
|
|
737
|
+
*/
|
|
738
|
+
export async function getLanguageListFromApi() {
|
|
739
|
+
try {
|
|
740
|
+
console.log("正在从接口获取语言列表...");
|
|
741
|
+
const response = await axios.get(
|
|
742
|
+
`http://new.sp.com/open-api/get_site_online_languages?site=${config.siteConfig.siteName}`
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
if (
|
|
746
|
+
response.data &&
|
|
747
|
+
response.data.success &&
|
|
748
|
+
response.data.result?.length
|
|
749
|
+
) {
|
|
750
|
+
const languageList = response.data.result;
|
|
751
|
+
console.log("成功获取语言列表:", languageList);
|
|
752
|
+
return languageList;
|
|
753
|
+
} else {
|
|
754
|
+
console.warn(
|
|
755
|
+
"接口返回数据格式不正确:",
|
|
756
|
+
response.data,
|
|
757
|
+
"将使用config中的语言列表"
|
|
758
|
+
);
|
|
759
|
+
return config.languageList;
|
|
760
|
+
}
|
|
761
|
+
} catch (error) {
|
|
762
|
+
throw new Error("从接口获取语言列表失败:" + error);
|
|
763
|
+
}
|
|
764
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pug-site-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.19",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"ws": "^8.18.0"
|
|
53
53
|
},
|
|
54
54
|
"license": "ISC",
|
|
55
|
-
"description": "
|
|
55
|
+
"description": "增加从接口获取语言列表、增加翻译所有语言指令、增加linux系统判断",
|
|
56
56
|
"files": [
|
|
57
57
|
"lib/",
|
|
58
58
|
"index.js"
|
|
@@ -60,4 +60,4 @@
|
|
|
60
60
|
"exports": {
|
|
61
61
|
".": "./index.js"
|
|
62
62
|
}
|
|
63
|
-
}
|
|
63
|
+
}
|