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 +5 -6
- package/lib/generate.js +21 -8
- package/lib/utils.js +273 -7
- package/package.json +5 -4
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
while (
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
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"
|