pug-site-core 2.0.20 → 2.0.22

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/devServer.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  matchESI,
11
11
  pathSymbol,
12
12
  getJsonData,
13
+ addTemplateScopeIsolation,
13
14
  } from "./utils.js";
14
15
  import http from "http";
15
16
  import WebSocket, { WebSocketServer } from "ws";
@@ -18,9 +19,8 @@ import { paths } from "./paths.js";
18
19
 
19
20
  const { config } = await import(paths.config);
20
21
  const pagsTemplatePath = paths.template.pages;
21
- const localIp = process.env._localIp || ip.address();
22
- const port =
23
- process.env._port || (await getIdleProt(config.devServer.port, localIp));
22
+ const localIp = ip.address();
23
+ const port = await getIdleProt(config.devServer.port);
24
24
  process.env._port = port;
25
25
  process.env._localIp = localIp;
26
26
 
@@ -208,6 +208,7 @@ function setupRoutes(app, wss) {
208
208
  if (config.isMatchEsi) {
209
209
  html = await matchESI(html, data);
210
210
  }
211
+ html = addTemplateScopeIsolation(html);
211
212
  res.send(_refreshScript + html);
212
213
  }
213
214
  }
@@ -248,7 +249,7 @@ async function matchRouter(url, language, device) {
248
249
 
249
250
  export async function startDevServer() {
250
251
  const server = createServer();
251
-
252
+ server.listen(port);
252
253
  console.log(
253
254
  "Listening:",
254
255
  `http://${localIp}:${port}`,
@@ -256,7 +257,5 @@ export async function startDevServer() {
256
257
  config.languageList[0]
257
258
  );
258
259
 
259
- server.listen(port);
260
-
261
260
  new Worker(paths.lib + "/watchFile.js");
262
261
  }
package/lib/generate.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  sleep,
8
8
  pathSymbol,
9
9
  obfuscateJavaScript,
10
+ addTemplateScopeIsolation,
10
11
  } from "./utils.js";
11
12
  import _ from "lodash";
12
13
  import async from "async";
@@ -35,22 +36,26 @@ export async function compilePagesPugToFn(pugPath) {
35
36
  ) {
36
37
  throw new Error("路径不存在! 注意路径前面会自动拼接/template/pages");
37
38
  }
38
-
39
39
  let compiledCode = lastPugFnStr;
40
-
40
+ if (config.isScopeIsolation) {
41
+ // 添加作用域处理函数到编译后的代码中
42
+ const scopeIsolationFn = `${addTemplateScopeIsolation.toString()}`;
43
+ compiledCode = compiledCode + scopeIsolationFn;
44
+ }
41
45
  // 使用async库并发编译pug文件
42
46
  await async.eachLimit(
43
47
  // 过滤出需要编译的文件
44
48
  pagesPugFilePathArr.filter(
45
49
  (fileName) => !pugPath || pathIsSame(pugPath, fileName)
46
50
  ),
47
- 12, // 限制并发数为10
51
+ 10, // 限制并发数为10
48
52
  async (fileName) => {
49
53
  const filePath = paths.resolveRoot(paths.template.pages, fileName);
50
- const funName = fileName.split(pathSymbol).join("_").slice(0, -4);
54
+ const funName = fileName.split(pathSymbol).join("_").slice(0, -4).replace(/[-]/g, "_");
51
55
 
52
56
  // 读取并编译pug文件
53
57
  const pugValue = await fse.readFile(filePath, "utf8");
58
+
54
59
  const fnStr = pug.compileClient(pugValue, {
55
60
  filename: filePath,
56
61
  basedir: paths.template.root,
@@ -68,7 +73,16 @@ export async function compilePagesPugToFn(pugPath) {
68
73
  }
69
74
 
70
75
  // 只提取函数定义部分并转换为ES模块格式
71
- const functionBody = fnStr.slice(functionStart, functionEnd);
76
+ let functionBody = fnStr.slice(functionStart, functionEnd);
77
+
78
+ if (config.isScopeIsolation) {
79
+ // 修改函数体,在返回HTML之前添加作用域处理
80
+ functionBody = functionBody.replace(
81
+ /return\s+(.*?);?\s*}$/,
82
+ `const html = $1;return addTemplateScopeIsolation(html);}`
83
+ );
84
+ }
85
+
72
86
  const exportFn = functionBody.replace(
73
87
  `function ${funName}(locals)`,
74
88
  `export function ${funName}(locals)`
@@ -127,7 +141,7 @@ export async function generateGetDataFn() {
127
141
  // 为每个页面注入数据获取函数
128
142
  await async.each(pagesPugFilePathArr, async (fileName) => {
129
143
  const funName =
130
- "get_" + fileName.split(pathSymbol).join("_").slice(0, -4) + "_data";
144
+ "get_" + fileName.split(pathSymbol).join("_").slice(0, -4).replace(/[-]/g, "_") + "_data";
131
145
 
132
146
  // 使用正则表达式检查特定的数据获取函数是否存在
133
147
  const dataFnRegex = new RegExp(
@@ -310,11 +324,10 @@ export async function fetchDataToJsonFile(args) {
310
324
  });
311
325
  }
312
326
  }
313
-
314
327
  // 处理模板页面数据
315
328
  for (const fileName of pugFilePathList) {
316
329
  let funName =
317
- "get_" + fileName.split(pathSymbol).join("_").slice(0, -4) + "_data";
330
+ "get_" + fileName.split(pathSymbol).join("_").slice(0, -4).replace(/[-]/g, "_") + "_data";
318
331
 
319
332
  let jsonFilePath = fileName.slice(0, -4).split(pathSymbol);
320
333
  if (!getData[funName] || typeof getData[funName] !== "function") {
package/lib/utils.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import fse from "fs-extra";
2
2
  import path from "path";
3
- import tcpPortUsed from "tcp-port-used";
4
3
  import JavaScriptObfuscator from "javascript-obfuscator";
5
4
  import { paths } from "./paths.js";
5
+ import detectPort from "detect-port";
6
+
6
7
 
7
8
  const { config } = await import(paths.config);
8
9
 
@@ -82,17 +83,21 @@ export async function sleep(ms) {
82
83
  /**
83
84
  * 获取可用的端口号
84
85
  * @param {number} port - 起始端口号
85
- * @param {string} Ip - IP地址
86
86
  * @returns {Promise<number>} 返回可用的端口号
87
87
  */
88
- export async function getIdleProt(port, Ip) {
89
- while (1) {
90
- if (!(await tcpPortUsed.check(port, Ip))) {
91
- break;
88
+ export async function getIdleProt(port) {
89
+ while (true) {
90
+ try {
91
+ // 使用detect-port检查端口是否可用
92
+ const availablePort = await detectPort(port);
93
+ if (Number(availablePort) === Number(port)) {
94
+ return port;
95
+ }
96
+ } catch (error) {
97
+ console.error(`端口检查出错: ${error.message}`);
92
98
  }
93
99
  port++;
94
100
  }
95
- return port;
96
101
  }
97
102
 
98
103
  /**
@@ -383,3 +388,264 @@ export async function getJsonData(jsonDataPath) {
383
388
  let jsonData = await fse.readJSON(filePath);
384
389
  return jsonData;
385
390
  }
391
+
392
+
393
+ /**
394
+ * 为HTML字符串中的template标签添加作用域隔离(优化版本)
395
+ * @param {string} htmlString - HTML字符串
396
+ * @param {string} scopePrefix - 作用域前缀,默认为"xy"
397
+ * @param {Object} options - 配置选项
398
+ * @param {number} options.maxDepth - 最大递归深度,默认为10
399
+ * @param {boolean} options.preserveComments - 是否保留注释,默认为true
400
+ * @returns {string} 处理后的HTML字符串
401
+ */
402
+ export function addTemplateScopeIsolation(htmlString, scopePrefix = "xy", options = {}) {
403
+ // 参数验证
404
+ if (!htmlString || typeof htmlString !== "string") {
405
+ return htmlString;
406
+ }
407
+
408
+ // 快速检查,避免不必要的处理
409
+ if (!htmlString.includes("<template>") || !htmlString.includes("</template>")) {
410
+ return htmlString;
411
+ }
412
+
413
+ // 配置选项
414
+ const config = {
415
+ maxDepth: options.maxDepth || 10,
416
+ preserveComments: options.preserveComments !== false,
417
+ _currentDepth: 0
418
+ };
419
+
420
+ // 生成唯一的作用域ID(使用更稳定的方法)
421
+ function generateScopeId(prefix) {
422
+ const timestamp = Date.now().toString(36);
423
+ const random = Math.random().toString(36).substring(2, 8);
424
+ return `${prefix}-${timestamp}-${random}`;
425
+ }
426
+
427
+ // 为简单选择器添加作用域(移到外部作用域)
428
+ function addScopeToSimpleSelector(selector, scopeId) {
429
+ // 处理空选择器
430
+ if (!selector || !selector.trim()) {
431
+ return selector;
432
+ }
433
+
434
+ selector = selector.trim();
435
+
436
+ // 处理伪类和伪元素(更完善的正则)
437
+ const pseudoMatch = selector.match(/^([^:]+?)(::?[^[]+)?$/);
438
+ if (pseudoMatch) {
439
+ const [, base, pseudo = ''] = pseudoMatch;
440
+ // 处理属性选择器
441
+ if (base.includes('[')) {
442
+ const attrMatch = base.match(/^([^[]+)(\[.+\])$/);
443
+ if (attrMatch) {
444
+ return `${attrMatch[1]}[data-${scopeId}]${attrMatch[2]}${pseudo}`;
445
+ }
446
+ }
447
+ return `${base}[data-${scopeId}]${pseudo}`;
448
+ }
449
+
450
+ return `${selector}[data-${scopeId}]`;
451
+ }
452
+
453
+ // 处理template标签
454
+ function parseHTML(html, depth = 0) {
455
+ // 检查递归深度
456
+ if (depth >= config.maxDepth) {
457
+ console.warn(`Maximum recursion depth (${config.maxDepth}) reached in template processing`);
458
+ return html;
459
+ }
460
+
461
+ // 使用更精确的模板匹配(避免贪婪匹配问题)
462
+ const templates = [];
463
+ let tempHtml = html;
464
+ let placeholder = 0;
465
+
466
+ // 先收集所有template标签的位置和内容
467
+ const templateRegex = /<template([^>]*)>([\s\S]*?)<\/template>/gi;
468
+ let match;
469
+
470
+ while ((match = templateRegex.exec(html)) !== null) {
471
+ templates.push({
472
+ full: match[0],
473
+ attributes: match[1] || '',
474
+ content: match[2] || '',
475
+ start: match.index,
476
+ placeholder: `__TEMPLATE_PLACEHOLDER_${placeholder++}__`
477
+ });
478
+ }
479
+
480
+ // 从后往前替换,避免索引偏移问题
481
+ for (let i = templates.length - 1; i >= 0; i--) {
482
+ const template = templates[i];
483
+ const scopeId = generateScopeId(scopePrefix);
484
+
485
+ // 处理内容
486
+ const processedContent = addScopeToHTML(template.content, scopeId);
487
+
488
+ // 创建新的div标签,保留原有属性
489
+ const attributesStr = template.attributes ? ` ${template.attributes.trim()}` : '';
490
+ const newDiv = `<div data-${scopeId}=""${attributesStr}>${processedContent}</div>`;
491
+
492
+ // 替换原始template
493
+ tempHtml = tempHtml.substring(0, template.start) +
494
+ newDiv +
495
+ tempHtml.substring(template.start + template.full.length);
496
+ }
497
+
498
+ // 检查是否还有嵌套的template需要处理
499
+ if (tempHtml.includes('<template>') && depth < config.maxDepth - 1) {
500
+ return parseHTML(tempHtml, depth + 1);
501
+ }
502
+
503
+ return tempHtml;
504
+ }
505
+
506
+ // 为HTML内容添加作用域属性
507
+ function addScopeToHTML(html, scopeId) {
508
+ // 处理HTML标签(改进的正则,支持自闭合标签)
509
+ let processedHtml = html.replace(
510
+ /<(\w+)([^>]*?)(\/?)>/g,
511
+ (match, tagName, attributes, selfClosing) => {
512
+ // 跳过已经有作用域属性的标签
513
+ if (attributes.includes('data-v-') ||
514
+ attributes.includes(`data-${scopeId}`) ||
515
+ attributes.includes(`data-${scopePrefix}-`)) {
516
+ return match;
517
+ }
518
+
519
+ // 处理特殊标签(不需要作用域的)
520
+ const skipTags = ['script', 'style', 'template'];
521
+ if (skipTags.includes(tagName.toLowerCase())) {
522
+ return match;
523
+ }
524
+
525
+ // 为标签添加作用域属性
526
+ return `<${tagName} data-${scopeId}=""${attributes}${selfClosing}>`;
527
+ }
528
+ );
529
+
530
+ // 处理style标签中的CSS
531
+ processedHtml = processedHtml.replace(
532
+ /<style([^>]*?)>([\s\S]*?)<\/style>/gi,
533
+ (match, styleAttrs, cssContent) => {
534
+ // 跳过已处理的CSS
535
+ if (cssContent.includes(`[data-${scopeId}]`) ||
536
+ cssContent.includes(`[data-${scopePrefix}-`)) {
537
+ return match;
538
+ }
539
+
540
+ const scopedCSS = addScopeToCSS(cssContent, scopeId);
541
+ return `<style${styleAttrs}>${scopedCSS}</style>`;
542
+ }
543
+ );
544
+
545
+ return processedHtml;
546
+ }
547
+
548
+ // 为CSS添加作用域(改进版本)
549
+ function addScopeToCSS(cssContent, scopeId) {
550
+ // 保存注释
551
+ const comments = [];
552
+ let commentIndex = 0;
553
+
554
+ if (config.preserveComments) {
555
+ cssContent = cssContent.replace(/\/\*[\s\S]*?\*\//g, (match) => {
556
+ const placeholder = `__CSS_COMMENT_${commentIndex++}__`;
557
+ comments.push({ placeholder, content: match });
558
+ return placeholder;
559
+ });
560
+ }
561
+
562
+ // 改进的CSS规则处理
563
+ function processCSS(css, level = 0) {
564
+ if (level > 5) return css; // 防止过深的嵌套
565
+
566
+ // 匹配CSS规则块
567
+ const ruleRegex = /([^{}]+)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g;
568
+
569
+ return css.replace(ruleRegex, (match, selectors, content) => {
570
+ const trimmedSelectors = selectors.trim();
571
+
572
+ // 处理@规则
573
+ if (trimmedSelectors.startsWith('@')) {
574
+ // @keyframes不需要作用域
575
+ if (trimmedSelectors.startsWith('@keyframes')) {
576
+ return match;
577
+ }
578
+
579
+ // @media, @supports等需要递归处理内部规则
580
+ if (content.includes('{')) {
581
+ const processedContent = processCSS(content, level + 1);
582
+ return `${selectors} {${processedContent}}`;
583
+ }
584
+
585
+ return match;
586
+ }
587
+
588
+ // 处理普通选择器
589
+ const processedSelectors = trimmedSelectors
590
+ .split(',')
591
+ .map(selector => {
592
+ selector = selector.trim();
593
+
594
+ // 跳过特殊情况
595
+ if (!selector ||
596
+ selector.startsWith('@') ||
597
+ /^(from|to|\d+%)$/.test(selector) ||
598
+ selector.includes(`[data-${scopeId}]`)) {
599
+ return selector;
600
+ }
601
+
602
+ // 清理现有的作用域属性
603
+ selector = selector.replace(/\[data-[a-z0-9-]+\]/gi, '').trim();
604
+
605
+ // 处理复杂选择器
606
+ const selectorParts = selector.split(/\s+/);
607
+
608
+ // 处理第一个选择器部分
609
+ if (selectorParts.length > 0) {
610
+ // 处理组合选择器(如 .class1.class2)
611
+ const firstPart = selectorParts[0];
612
+ const combinedSelectors = firstPart.split(/(?=[.#[])/);
613
+
614
+ if (combinedSelectors.length > 1) {
615
+ // 在第一个实际选择器后添加作用域
616
+ combinedSelectors[0] = addScopeToSimpleSelector(combinedSelectors[0], scopeId);
617
+ selectorParts[0] = combinedSelectors.join('');
618
+ } else {
619
+ selectorParts[0] = addScopeToSimpleSelector(firstPart, scopeId);
620
+ }
621
+ }
622
+
623
+ return selectorParts.join(' ');
624
+ })
625
+ .join(', ');
626
+
627
+ return `${processedSelectors} {${content}}`;
628
+ });
629
+ }
630
+
631
+ // 处理CSS
632
+ let processedCSS = processCSS(cssContent);
633
+
634
+ // 恢复注释
635
+ if (config.preserveComments) {
636
+ comments.forEach(({ placeholder, content }) => {
637
+ processedCSS = processedCSS.replace(placeholder, content);
638
+ });
639
+ }
640
+
641
+ return processedCSS;
642
+ }
643
+
644
+ // 开始处理
645
+ try {
646
+ return parseHTML(htmlString);
647
+ } catch (error) {
648
+ console.error('Error in addTemplateScopeIsolation:', error);
649
+ return htmlString; // 出错时返回原始内容
650
+ }
651
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pug-site-core",
3
- "version": "2.0.20",
3
+ "version": "2.0.22",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,7 +21,10 @@
21
21
  "@google-cloud/translate": "^8.5.0",
22
22
  "async": "^3.2.6",
23
23
  "axios": "^1.7.7",
24
+ "cheerio": "^1.0.0",
24
25
  "chokidar": "^3.6.0",
26
+ "css": "^3.0.0",
27
+ "detect-port": "^2.1.0",
25
28
  "express": "^4.19.2",
26
29
  "express-useragent": "^1.0.15",
27
30
  "fs-extra": "^11.2.0",
@@ -37,16 +40,14 @@
37
40
  "jstransformer-autoprefixer": "^2.0.0",
38
41
  "jstransformer-less": "^2.3.0",
39
42
  "jstransformer-scss": "^2.0.0",
40
- "less": "^4.2.0",
41
43
  "lodash": "^4.17.21",
42
44
  "nodemon": "^3.1.4",
43
45
  "pug": "^3.0.3",
44
- "tcp-port-used": "^1.0.2",
45
46
  "uglify-js": "^3.19.3",
46
47
  "ws": "^8.18.0"
47
48
  },
48
49
  "license": "ISC",
49
- "description": "再次修复错误的删除文件问题",
50
+ "description": "ai优化一版pug文件组件作用域构建",
50
51
  "files": [
51
52
  "lib/",
52
53
  "index.js"