pug-site-core 3.0.24 → 3.0.26

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.
Files changed (2) hide show
  1. package/lib/generate.js +439 -5
  2. package/package.json +2 -2
package/lib/generate.js CHANGED
@@ -26,7 +26,58 @@ import { fetchABTestInfo } from "./utils.js";
26
26
  const { config } = await import(paths.config);
27
27
 
28
28
  /**
29
- * 将pages目录下的pug模板编译为JS函数
29
+ * 编译单个 pug 文件
30
+ * @param {string} fileName - pug 文件名(相对于 template/pages)
31
+ * @param {string} filePath - 完整文件路径
32
+ * @returns {Promise<string>} 编译后的函数代码
33
+ */
34
+ async function compileSinglePugFile(fileName, filePath) {
35
+ const funName = fileName
36
+ .split(pathSymbol)
37
+ .join("_")
38
+ .slice(0, -4)
39
+ .replaceAll(/[-]/g, "_");
40
+
41
+ // 读取并编译pug文件
42
+ const pugValue = await fse.readFile(filePath, "utf8");
43
+
44
+ const fnStr = pug.compileClient(pugValue, {
45
+ filename: filePath,
46
+ basedir: paths.template.root,
47
+ compileDebug: true,
48
+ name: funName,
49
+ filters: getCompilePugFilter()
50
+ });
51
+
52
+ // 提取函数定义部分
53
+ const functionStart = fnStr.indexOf(`function ${funName}(locals)`);
54
+ const functionEnd = fnStr.lastIndexOf("}") + 1;
55
+
56
+ if (functionStart === -1) {
57
+ throw new Error(`无法在编译结果中找到函数 ${funName}`);
58
+ }
59
+
60
+ // 只提取函数定义部分并转换为ES模块格式
61
+ let functionBody = fnStr.slice(functionStart, functionEnd);
62
+
63
+ if (config.isScopeIsolation) {
64
+ // 修改函数体,在返回HTML之前添加作用域处理
65
+ functionBody = functionBody.replace(
66
+ /return\s+(.*?);?\s*}$/,
67
+ `const html = $1;return addTemplateScopeIsolation(html);}`
68
+ );
69
+ }
70
+
71
+ const exportFn = functionBody.replace(
72
+ `function ${funName}(locals)`,
73
+ `export function ${funName}(locals)`
74
+ );
75
+
76
+ return exportFn;
77
+ }
78
+
79
+ /**
80
+ * 将pages目录下的pug模板编译为JS函数(原有逻辑,全量编译)
30
81
  * @param {string} pugPath - 指定编译的pug文件路径(相对于/template/pages)
31
82
  * @throws {Error} 当路径不存在或编译失败时抛出错误
32
83
  * @returns {Promise<void>}
@@ -214,11 +265,13 @@ export async function fetchDataToJsonFile(args) {
214
265
 
215
266
  let saveJsonData = async (filePath, data) => {
216
267
  let finalPath = paths.resolveRoot("jsonData", filePath);
217
- // await fse.remove(finalPath);
268
+
218
269
  // 如果 路径存在,跳过写入
219
- if (await fse.pathExists(finalPath)) {
270
+ if (args.includes("skip") && await fse.pathExists(finalPath)) {
220
271
  return;
221
272
  }
273
+
274
+ await fse.remove(finalPath);
222
275
  await fse.outputJson(finalPath, data);
223
276
  };
224
277
 
@@ -253,8 +306,8 @@ export async function fetchDataToJsonFile(args) {
253
306
  return Promise.resolve();
254
307
  };
255
308
 
256
- // 如果没有过滤条件,清空输出目录
257
- if (!filterFun.length && !filterLang.length) {
309
+ // 如果没有过滤条件且不是skip模式,清空输出目录
310
+ if (!filterFun.length && !filterLang.length && !args.includes("skip")) {
258
311
  await fse.remove(paths.resolveRoot("jsonData"));
259
312
  }
260
313
 
@@ -557,6 +610,387 @@ export async function buildFn() {
557
610
  console.log("打包完成花费:", (Date.now() - starTime) / 1000, "s");
558
611
  }
559
612
 
613
+ // ============================================
614
+ // buildSrc 专用函数(增量构建、依赖追踪、跳过压缩)
615
+ // ============================================
616
+
617
+ /**
618
+ * 获取编译缓存文件路径(buildSrc 专用)
619
+ * @returns {string} 缓存文件路径
620
+ */
621
+ function getCompileCachePath() {
622
+ return paths.resolveRoot("pagesPugFn", ".compile-cache.json");
623
+ }
624
+
625
+ /**
626
+ * 加载编译缓存(buildSrc 专用)
627
+ * @returns {Promise<Object>} 缓存对象 { [fileName]: { mtime: number, depMtime: number, code: string } }
628
+ */
629
+ async function loadCompileCache() {
630
+ const cachePath = getCompileCachePath();
631
+ try {
632
+ if (await fse.pathExists(cachePath)) {
633
+ return await fse.readJSON(cachePath);
634
+ }
635
+ } catch (error) {
636
+ console.warn("读取编译缓存失败,将全量编译:", error.message);
637
+ }
638
+ return {};
639
+ }
640
+
641
+ /**
642
+ * 保存编译缓存(buildSrc 专用)
643
+ * @param {Object} cache - 缓存对象
644
+ * @returns {Promise<void>}
645
+ */
646
+ async function saveCompileCache(cache) {
647
+ const cachePath = getCompileCachePath();
648
+ try {
649
+ await fse.ensureFile(cachePath);
650
+ await fse.writeJSON(cachePath, cache, { spaces: 2 });
651
+ } catch (error) {
652
+ console.warn("保存编译缓存失败:", error.message);
653
+ }
654
+ }
655
+
656
+ /**
657
+ * 获取文件的修改时间(buildSrc 专用)
658
+ * @param {string} filePath - 文件路径
659
+ * @returns {Promise<number>} 修改时间戳(毫秒)
660
+ */
661
+ async function getFileMtime(filePath) {
662
+ try {
663
+ const stats = await fse.stat(filePath);
664
+ return stats.mtimeMs || stats.mtime.getTime();
665
+ } catch (error) {
666
+ return 0;
667
+ }
668
+ }
669
+
670
+ /**
671
+ * 解析 pug 文件中的依赖关系(extends, include)(buildSrc 专用)
672
+ * @param {string} filePath - pug 文件路径
673
+ * @param {string} content - pug 文件内容
674
+ * @param {string} baseDir - 基础目录(template 根目录)
675
+ * @returns {string[]} 依赖的文件路径数组(相对于 template 根目录)
676
+ */
677
+ function parsePugDependencies(filePath, content, baseDir) {
678
+ const dependencies = [];
679
+ const lines = content.split('\n');
680
+
681
+ for (const line of lines) {
682
+ const trimmed = line.trim();
683
+
684
+ // 匹配 extends 语句: extends /common/base.pug
685
+ const extendsMatch = trimmed.match(/^extends\s+(.+)$/);
686
+ if (extendsMatch) {
687
+ const depPath = extendsMatch[1].trim();
688
+ const resolvedPath = resolvePugPath(depPath, baseDir);
689
+ if (resolvedPath) {
690
+ dependencies.push(resolvedPath);
691
+ }
692
+ }
693
+
694
+ // 匹配 include 语句: include /components/ga_head.pug 或 include ./header.pug
695
+ const includeMatch = trimmed.match(/^include(?::\w+)?\s+(.+)$/);
696
+ if (includeMatch) {
697
+ const depPath = includeMatch[1].trim();
698
+ const resolvedPath = resolvePugPath(depPath, baseDir, path.dirname(filePath));
699
+ if (resolvedPath) {
700
+ dependencies.push(resolvedPath);
701
+ }
702
+ }
703
+ }
704
+
705
+ return dependencies;
706
+ }
707
+
708
+ /**
709
+ * 解析 pug 路径为相对于 template 根目录的路径(buildSrc 专用)
710
+ * @param {string} depPath - 依赖路径(可能是绝对路径或相对路径)
711
+ * @param {string} baseDir - template 根目录
712
+ * @param {string} currentDir - 当前文件所在目录(用于解析相对路径)
713
+ * @returns {string|null} 解析后的路径(相对于 template 根目录),如果不是 pug 文件则返回 null
714
+ */
715
+ function resolvePugPath(depPath, baseDir, currentDir = null) {
716
+ depPath = depPath.replace(/^["']|["']$/g, '').trim();
717
+
718
+ if (!depPath.endsWith('.pug')) {
719
+ return null;
720
+ }
721
+
722
+ let resolvedPath;
723
+ if (depPath.startsWith('/')) {
724
+ resolvedPath = path.join(baseDir, depPath);
725
+ } else if (currentDir) {
726
+ resolvedPath = path.resolve(currentDir, depPath);
727
+ } else {
728
+ resolvedPath = path.join(baseDir, depPath);
729
+ }
730
+
731
+ const relativePath = path.relative(baseDir, resolvedPath);
732
+ return relativePath.replace(/\\/g, '/');
733
+ }
734
+
735
+ /**
736
+ * 获取文件的所有依赖(递归)(buildSrc 专用)
737
+ * @param {string} filePath - 文件路径(相对于 template/pages)
738
+ * @param {Set<string>} visited - 已访问的文件集合(防止循环依赖)
739
+ * @param {Map<string, string[]>} dependencyCache - 依赖缓存
740
+ * @returns {Promise<Set<string>>} 所有依赖的文件路径集合(相对于 template 根目录)
741
+ */
742
+ async function getAllDependencies(filePath, visited = new Set(), dependencyCache = new Map()) {
743
+ const templateRoot = paths.resolveRoot(paths.template.root);
744
+ const cacheKey = filePath;
745
+
746
+ if (visited.has(cacheKey)) {
747
+ return new Set();
748
+ }
749
+ visited.add(cacheKey);
750
+
751
+ let fullPath;
752
+ if (filePath.startsWith('common/') || filePath.startsWith('components/')) {
753
+ fullPath = paths.resolveRoot(paths.template.root, filePath);
754
+ } else {
755
+ fullPath = paths.resolveRoot(paths.template.pages, filePath);
756
+ }
757
+
758
+ if (dependencyCache.has(cacheKey)) {
759
+ const deps = dependencyCache.get(cacheKey);
760
+ const allDeps = new Set(deps);
761
+ for (const dep of deps) {
762
+ const depDeps = await getAllDependencies(dep, visited, dependencyCache);
763
+ depDeps.forEach(d => allDeps.add(d));
764
+ }
765
+ return allDeps;
766
+ }
767
+
768
+ let content;
769
+ try {
770
+ if (!await fse.pathExists(fullPath)) {
771
+ return new Set();
772
+ }
773
+ content = await fse.readFile(fullPath, 'utf8');
774
+ } catch (error) {
775
+ console.warn(`无法读取文件 ${filePath}:`, error.message);
776
+ return new Set();
777
+ }
778
+
779
+ const currentDir = path.dirname(fullPath);
780
+ const dependencies = parsePugDependencies(fullPath, content, templateRoot);
781
+ dependencyCache.set(cacheKey, dependencies);
782
+
783
+ const allDependencies = new Set(dependencies);
784
+ for (const dep of dependencies) {
785
+ const depDeps = await getAllDependencies(dep, visited, dependencyCache);
786
+ depDeps.forEach(d => allDependencies.add(d));
787
+ }
788
+
789
+ return allDependencies;
790
+ }
791
+
792
+ /**
793
+ * 将pages目录下的pug模板编译为JS函数(buildSrc 专用:增量构建、依赖追踪、跳过压缩)
794
+ * @param {string} pugPath - 指定编译的pug文件路径(相对于/template/pages)
795
+ * @throws {Error} 当路径不存在或编译失败时抛出错误
796
+ * @returns {Promise<void>}
797
+ */
798
+ async function compilePagesPugToFnForBuildSrc(pugPath) {
799
+ try {
800
+ const pagesPugFilePathArr = await getPagesPugFilePathArr();
801
+
802
+ if (
803
+ pugPath &&
804
+ !fse.pathExistsSync(paths.resolveRoot(paths.template.pages, pugPath))
805
+ ) {
806
+ throw new Error("路径不存在! 注意路径前面会自动拼接/template/pages");
807
+ }
808
+
809
+ const filesToProcess = pagesPugFilePathArr.filter(
810
+ (fileName) => !pugPath || pathIsSame(pugPath, fileName)
811
+ );
812
+
813
+ const cache = await loadCompileCache();
814
+ const newCache = {};
815
+ const filesToCompile = [];
816
+ const cachedCode = {};
817
+ const dependencyCache = new Map();
818
+
819
+ // 第一步:检查文件本身的修改时间(并行)
820
+ const checkStartTime = Date.now();
821
+ console.log("检查文件修改状态...");
822
+ const fileMtimes = new Map();
823
+ await async.eachLimit(
824
+ filesToProcess,
825
+ 20,
826
+ async (fileName) => {
827
+ const filePath = paths.resolveRoot(paths.template.pages, fileName);
828
+ const mtime = await getFileMtime(filePath);
829
+ fileMtimes.set(fileName, mtime);
830
+ }
831
+ );
832
+ const checkTime = Date.now() - checkStartTime;
833
+
834
+ // 第二步:检查依赖文件的修改时间(并行)
835
+ const depStartTime = Date.now();
836
+ console.log("分析文件依赖关系...");
837
+ const dependencyMtimes = new Map();
838
+ const depFileMtimeCache = new Map();
839
+
840
+ await async.eachLimit(
841
+ filesToProcess,
842
+ 10,
843
+ async (fileName) => {
844
+ try {
845
+ const dependencies = await getAllDependencies(fileName, new Set(), dependencyCache);
846
+
847
+ let maxDepMtime = 0;
848
+ for (const dep of dependencies) {
849
+ const depPath = paths.resolveRoot(paths.template.root, dep);
850
+
851
+ let depMtime;
852
+ if (depFileMtimeCache.has(depPath)) {
853
+ depMtime = depFileMtimeCache.get(depPath);
854
+ } else {
855
+ depMtime = await getFileMtime(depPath);
856
+ depFileMtimeCache.set(depPath, depMtime);
857
+ }
858
+
859
+ if (depMtime > 0) {
860
+ maxDepMtime = Math.max(maxDepMtime, depMtime);
861
+ }
862
+ }
863
+
864
+ if (maxDepMtime > 0) {
865
+ dependencyMtimes.set(fileName, maxDepMtime);
866
+ }
867
+ } catch (error) {
868
+ console.warn(`分析文件 ${fileName} 的依赖关系失败:`, error.message);
869
+ }
870
+ }
871
+ );
872
+ const depTime = Date.now() - depStartTime;
873
+
874
+ // 第三步:决定哪些文件需要重新编译
875
+ for (const fileName of filesToProcess) {
876
+ const mtime = fileMtimes.get(fileName);
877
+ const depMtime = dependencyMtimes.get(fileName) || 0;
878
+ const cached = cache[fileName];
879
+
880
+ const needsRecompile = !cached ||
881
+ cached.mtime !== mtime ||
882
+ (depMtime > 0 && (!cached.depMtime || cached.depMtime < depMtime));
883
+
884
+ if (needsRecompile) {
885
+ filesToCompile.push(fileName);
886
+ } else {
887
+ cachedCode[fileName] = cached.code;
888
+ newCache[fileName] = cached;
889
+ }
890
+ }
891
+
892
+ const compileCount = filesToCompile.length;
893
+ const cacheCount = Object.keys(cachedCode).length;
894
+
895
+ if (compileCount > 0) {
896
+ console.log(`需要编译 ${compileCount} 个文件,使用缓存 ${cacheCount} 个文件`);
897
+ console.log(` 检查文件耗时: ${(checkTime / 1000).toFixed(2)}s, 分析依赖耗时: ${(depTime / 1000).toFixed(2)}s`);
898
+ } else {
899
+ console.log(`所有文件均使用缓存,跳过编译 (${cacheCount} 个文件)`);
900
+ console.log(` 检查文件耗时: ${(checkTime / 1000).toFixed(2)}s, 分析依赖耗时: ${(depTime / 1000).toFixed(2)}s`);
901
+ }
902
+
903
+ const lastPugFnStr = await fse.readFile(paths.pugRuntime, "utf8");
904
+ let compiledCode = lastPugFnStr;
905
+ if (config.isScopeIsolation) {
906
+ const scopeIsolationFn = `${addTemplateScopeIsolation.toString()}`;
907
+ compiledCode = compiledCode + scopeIsolationFn;
908
+ }
909
+
910
+ // 并发编译需要重新编译的文件
911
+ const compileStartTime = Date.now();
912
+ if (filesToCompile.length > 0) {
913
+ await async.eachLimit(
914
+ filesToCompile,
915
+ 20,
916
+ async (fileName) => {
917
+ const filePath = paths.resolveRoot(paths.template.pages, fileName);
918
+ const mtime = fileMtimes.get(fileName);
919
+
920
+ try {
921
+ const exportFn = await compileSinglePugFile(fileName, filePath);
922
+ compiledCode += exportFn;
923
+
924
+ const depMtime = dependencyMtimes.get(fileName) || 0;
925
+ newCache[fileName] = {
926
+ mtime: mtime,
927
+ depMtime: depMtime,
928
+ code: exportFn
929
+ };
930
+ } catch (error) {
931
+ console.error(`编译文件 ${fileName} 失败:`, error);
932
+ throw error;
933
+ }
934
+ }
935
+ );
936
+ }
937
+ const compileTime = Date.now() - compileStartTime;
938
+ if (compileCount > 0) {
939
+ console.log(` 编译文件耗时: ${(compileTime / 1000).toFixed(2)}s`);
940
+ }
941
+
942
+ // 添加缓存的代码
943
+ for (const fileName of filesToProcess) {
944
+ if (cachedCode[fileName] && !filesToCompile.includes(fileName)) {
945
+ compiledCode += cachedCode[fileName];
946
+ if (!newCache[fileName]) {
947
+ newCache[fileName] = cache[fileName];
948
+ }
949
+ }
950
+ }
951
+
952
+ // 保存新的缓存
953
+ await saveCompileCache(newCache);
954
+
955
+ // buildSrc 专用:跳过压缩以提升开发环境构建速度
956
+ console.log(`跳过代码压缩(buildSrc 模式,可节省 ~4-5 秒)`);
957
+
958
+ // 写入最终文件
959
+ const outputPath = paths.resolveRoot("pagesPugFn", "index.js");
960
+ await fse.ensureFile(outputPath);
961
+ await fse.writeFile(outputPath, compiledCode);
962
+
963
+ if (compileCount > 0) {
964
+ console.log(`编译完成: 新编译 ${compileCount} 个,缓存 ${cacheCount} 个`);
965
+ }
966
+ } catch (error) {
967
+ console.error("编译PUG模板失败:", error);
968
+ throw error;
969
+ }
970
+ }
971
+
972
+ /**
973
+ * 构建 pages.js 到 src/ 目录
974
+ * 用于开发环境,让 wrangler 可以只监听 src/ 目录的变化
975
+ * 注意:common.json 的生成逻辑在 build-src.js 中实现,不在此处
976
+ * @returns {Promise<void>}
977
+ */
978
+ export async function buildSrc() {
979
+ console.log("开始构建 src/ 目录...");
980
+ let starTime = Date.now();
981
+
982
+ // 编译 pug 模板到函数(buildSrc 专用:增量构建、依赖追踪、跳过压缩)
983
+ await compilePagesPugToFnForBuildSrc();
984
+
985
+ // 复制编译后的 pages.js 到 src/pages.js
986
+ await fse.copy(
987
+ paths.resolveRoot("pagesPugFn/index.js"),
988
+ paths.resolveRoot("src/pages.js")
989
+ );
990
+
991
+ console.log("构建 src/ 目录完成,花费:", (Date.now() - starTime) / 1000, "s");
992
+ }
993
+
560
994
  //html文件打包
561
995
  export async function buildStatic() {
562
996
  let jsonDataPath = paths.resolveRoot("jsonData");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pug-site-core",
3
- "version": "3.0.24",
3
+ "version": "3.0.26",
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": "getData不删旧数据,增量写文件",
55
+ "description": "重构 Pug 模板编译流程以支持增量构建和依赖追踪,并新增开发环境专用的 `buildSrc` 函数。",
56
56
  "files": [
57
57
  "lib/",
58
58
  "index.js"