@zealakit/css-doctor-next 1.0.0 → 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zealamic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -399,7 +399,10 @@ var CssDoctorWebpackPlugin = class {
399
399
  pageUsage[page.name] = classes;
400
400
  pageUsageCounts[page.name] = classes.length;
401
401
  }
402
- const pageCoverageApplied = this.pageCoverage.enabled && pageNames.length > 0;
402
+ const hasPageClasses = pagesResult.pages.some(
403
+ (page) => page.classes.size > 0
404
+ );
405
+ const pageCoverageApplied = this.pageCoverage.enabled && hasPageClasses;
403
406
  const diagnostics = {
404
407
  plugin: "css-doctor-next",
405
408
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -488,7 +491,7 @@ var CssDoctorWebpackPlugin = class {
488
491
  cssContent: originalCss,
489
492
  pageUsage,
490
493
  pageCoverage: {
491
- enabled: this.pageCoverage.enabled,
494
+ enabled: pageCoverageApplied,
492
495
  minPageRatio: this.pageCoverage.minPageRatio,
493
496
  forceCommon: this.pageCoverage.forceCommon
494
497
  },
@@ -748,6 +751,7 @@ async function collectPages(compilation, directPageUsage) {
748
751
  const pageEntries = [];
749
752
  const sharedEntries = [];
750
753
  const fallbackEntries = [];
754
+ const loaderFallbackEntries = [];
751
755
  for (const entry of entries) {
752
756
  const name = entryName(entry);
753
757
  if (!name) {
@@ -764,12 +768,19 @@ async function collectPages(compilation, directPageUsage) {
764
768
  name: routeNameForEntry(name) ?? name,
765
769
  sourceName: name
766
770
  });
771
+ } else {
772
+ loaderFallbackEntries.push({
773
+ entry,
774
+ name: routeNameForEntry(name) ?? name,
775
+ sourceName: name
776
+ });
767
777
  }
768
778
  }
769
779
  const selectedPages = pageEntries.length > 0 ? pageEntries : fallbackEntries;
770
780
  const direct = directPageUsage ? Object.keys(directPageUsage).length > 0 : false;
771
781
  const pages = /* @__PURE__ */ new Map();
772
782
  const pagesByCss = /* @__PURE__ */ new Map();
783
+ const projectRoot = compilation.compiler?.context ?? process.cwd();
773
784
  if (direct && directPageUsage) {
774
785
  for (const [rawName, classes] of Object.entries(directPageUsage)) {
775
786
  const name = rawName.trim();
@@ -783,10 +794,27 @@ async function collectPages(compilation, directPageUsage) {
783
794
  jsFiles: /* @__PURE__ */ new Set()
784
795
  });
785
796
  }
797
+ attachEntryAssetsToPages(pages, selectedPages);
798
+ const orphanPages = [...pages.values()].filter(
799
+ (page) => page.jsFiles.size === 0
800
+ );
801
+ if (orphanPages.length > 0) {
802
+ const loaderEntries = selectedPages.length > 0 ? selectedPages : loaderFallbackEntries;
803
+ const fallbackJs = /* @__PURE__ */ new Set();
804
+ for (const { entry } of loaderEntries) {
805
+ for (const jsFile of collectEntryJsFiles(entry)) {
806
+ fallbackJs.add(jsFile);
807
+ }
808
+ }
809
+ for (const page of orphanPages) {
810
+ for (const jsFile of fallbackJs) {
811
+ page.jsFiles.add(jsFile);
812
+ }
813
+ }
814
+ }
786
815
  } else {
787
816
  const sourceCache = /* @__PURE__ */ new Map();
788
817
  const sharedClasses = /* @__PURE__ */ new Set();
789
- const projectRoot = process.cwd();
790
818
  for (const entry of sharedEntries) {
791
819
  for (const className of await collectEntryClasses(
792
820
  compilation,
@@ -839,19 +867,10 @@ async function collectPages(compilation, directPageUsage) {
839
867
  const targetPages = [...pages.keys()];
840
868
  for (const { entry, name } of selectedPages) {
841
869
  const cssFiles = collectEntryCssFiles(entry);
842
- if (direct) {
843
- const directPage = pages.get(name);
844
- if (directPage) {
845
- for (const cssFile of cssFiles) {
846
- directPage.cssFiles.add(cssFile);
847
- }
848
- }
849
- } else {
850
- const page = pages.get(name);
851
- if (page) {
852
- for (const cssFile of cssFiles) {
853
- page.cssFiles.add(cssFile);
854
- }
870
+ const page = findPageForEntry(pages, name);
871
+ if (page) {
872
+ for (const cssFile of cssFiles) {
873
+ page.cssFiles.add(cssFile);
855
874
  }
856
875
  }
857
876
  }
@@ -876,18 +895,46 @@ async function collectPages(compilation, directPageUsage) {
876
895
  pagesByCss
877
896
  };
878
897
  }
898
+ function attachEntryAssetsToPages(pages, selectedPages) {
899
+ for (const { entry, name } of selectedPages) {
900
+ const page = findPageForEntry(pages, name);
901
+ if (!page) {
902
+ continue;
903
+ }
904
+ for (const jsFile of collectEntryJsFiles(entry)) {
905
+ page.jsFiles.add(jsFile);
906
+ }
907
+ for (const cssFile of collectEntryCssFiles(entry)) {
908
+ page.cssFiles.add(cssFile);
909
+ }
910
+ }
911
+ }
912
+ function findPageForEntry(pages, entryNameValue) {
913
+ const direct = pages.get(entryNameValue);
914
+ if (direct) {
915
+ return direct;
916
+ }
917
+ const normalized = normalizeSlashes(entryNameValue).replace(/^\/+/, "");
918
+ const bare = normalized.replace(/^\//, "");
919
+ return pages.get(normalized) ?? pages.get(`/${bare}`) ?? pages.get(bare) ?? pages.get(bare.replace(/\//g, "-"));
920
+ }
879
921
  async function collectPageSourceClasses(entryName2, projectRoot, sourceCache) {
880
922
  const classes = /* @__PURE__ */ new Set();
881
923
  const visited = /* @__PURE__ */ new Set();
882
924
  const normalized = normalizeSlashes(entryName2);
925
+ const roots = projectSourceRoots(projectRoot);
883
926
  const candidates = [];
884
927
  if (normalized.startsWith("app/")) {
885
928
  const path = normalized.slice(4);
886
929
  if (path === "page" || path.endsWith("/page")) {
887
930
  const pagePath = path === "page" ? "app/page" : `app/${path}`;
888
- candidates.push((0, import_node_path2.resolve)(projectRoot, pagePath));
931
+ for (const root of roots) {
932
+ candidates.push((0, import_node_path2.resolve)(root, pagePath));
933
+ }
889
934
  if (path === "_not-found/page") {
890
- candidates.push((0, import_node_path2.resolve)(projectRoot, "app/not-found"));
935
+ for (const root of roots) {
936
+ candidates.push((0, import_node_path2.resolve)(root, "app/not-found"));
937
+ }
891
938
  }
892
939
  const directories = path.split("/").slice(0, -1).filter(Boolean);
893
940
  const layoutPaths = ["app"];
@@ -895,16 +942,24 @@ async function collectPageSourceClasses(entryName2, projectRoot, sourceCache) {
895
942
  layoutPaths.push(`app/${directories.slice(0, index + 1).join("/")}`);
896
943
  }
897
944
  for (const layoutPath of layoutPaths) {
898
- candidates.push((0, import_node_path2.resolve)(projectRoot, `${layoutPath}/layout`));
945
+ for (const root of roots) {
946
+ candidates.push((0, import_node_path2.resolve)(root, `${layoutPath}/layout`));
947
+ }
899
948
  }
900
949
  } else {
901
- candidates.push((0, import_node_path2.resolve)(projectRoot, normalized));
950
+ for (const root of roots) {
951
+ candidates.push((0, import_node_path2.resolve)(root, normalized));
952
+ }
902
953
  }
903
954
  } else if (normalized.startsWith("pages/")) {
904
- candidates.push((0, import_node_path2.resolve)(projectRoot, normalized));
905
- candidates.push((0, import_node_path2.resolve)(projectRoot, "pages/_app"));
955
+ for (const root of roots) {
956
+ candidates.push((0, import_node_path2.resolve)(root, normalized));
957
+ candidates.push((0, import_node_path2.resolve)(root, "pages/_app"));
958
+ }
906
959
  } else {
907
- candidates.push((0, import_node_path2.resolve)(projectRoot, normalized));
960
+ for (const root of roots) {
961
+ candidates.push((0, import_node_path2.resolve)(root, normalized));
962
+ }
908
963
  }
909
964
  for (const candidate of candidates) {
910
965
  const file = findSourceFile(candidate);
@@ -917,13 +972,23 @@ async function collectPageSourceClasses(entryName2, projectRoot, sourceCache) {
917
972
  async function collectNameSourceClasses(entryName2, projectRoot, sourceCache) {
918
973
  const classes = /* @__PURE__ */ new Set();
919
974
  const visited = /* @__PURE__ */ new Set();
920
- const candidate = (0, import_node_path2.resolve)(projectRoot, normalizeSlashes(entryName2));
921
- const file = findSourceFile(candidate);
922
- if (file) {
923
- await scanSourceFile(file, classes, projectRoot, sourceCache, visited);
975
+ const relative = normalizeSlashes(entryName2);
976
+ for (const root of projectSourceRoots(projectRoot)) {
977
+ const file = findSourceFile((0, import_node_path2.resolve)(root, relative));
978
+ if (file) {
979
+ await scanSourceFile(file, classes, projectRoot, sourceCache, visited);
980
+ }
924
981
  }
925
982
  return classes;
926
983
  }
984
+ function projectSourceRoots(projectRoot) {
985
+ const roots = [projectRoot];
986
+ const srcRoot = (0, import_node_path2.resolve)(projectRoot, "src");
987
+ if (srcRoot !== projectRoot && (0, import_node_fs2.existsSync)(srcRoot)) {
988
+ roots.push(srcRoot);
989
+ }
990
+ return roots;
991
+ }
927
992
  async function scanSourceFile(filePath, classes, projectRoot, sourceCache, visited) {
928
993
  const absolutePath = (0, import_node_path2.resolve)(filePath);
929
994
  if (visited.has(absolutePath)) {
@@ -1007,18 +1072,28 @@ function extractImportSpecifiers(source) {
1007
1072
  return [...specifiers];
1008
1073
  }
1009
1074
  function resolveImportSpecifier(specifier, fromFile, projectRoot) {
1010
- let basePath;
1011
1075
  if (specifier.startsWith("@/")) {
1012
- basePath = (0, import_node_path2.resolve)(projectRoot, specifier.slice(2));
1013
- } else if (specifier.startsWith("./") || specifier.startsWith("../")) {
1014
- basePath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(fromFile), specifier);
1015
- } else {
1076
+ const relative = specifier.slice(2);
1077
+ for (const root of projectSourceRoots(projectRoot)) {
1078
+ const basePath = (0, import_node_path2.resolve)(root, relative);
1079
+ if (isStyleFile(basePath)) {
1080
+ return null;
1081
+ }
1082
+ const found = findSourceFile(basePath);
1083
+ if (found) {
1084
+ return found;
1085
+ }
1086
+ }
1016
1087
  return null;
1017
1088
  }
1018
- if (isStyleFile(basePath)) {
1019
- return null;
1089
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
1090
+ const basePath = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(fromFile), specifier);
1091
+ if (isStyleFile(basePath)) {
1092
+ return null;
1093
+ }
1094
+ return findSourceFile(basePath);
1020
1095
  }
1021
- return findSourceFile(basePath);
1096
+ return null;
1022
1097
  }
1023
1098
  async function collectEntryClasses(compilation, entry, sourceCache) {
1024
1099
  const classes = /* @__PURE__ */ new Set();
@@ -1187,13 +1262,22 @@ function scanClasses(source, classes) {
1187
1262
  continue;
1188
1263
  }
1189
1264
  for (const token of value.split(/\s+/)) {
1190
- if (/^-?[_a-zA-Z][\w-]*$/.test(token)) {
1265
+ if (isClassToken(token)) {
1191
1266
  classes.add(token.toLowerCase());
1192
1267
  }
1193
1268
  }
1194
1269
  }
1195
1270
  }
1196
1271
  }
1272
+ function isClassToken(token) {
1273
+ if (!token || token.length > 256) {
1274
+ return false;
1275
+ }
1276
+ if (/[`${}]/.test(token)) {
1277
+ return false;
1278
+ }
1279
+ return /[A-Za-z_]/.test(token);
1280
+ }
1197
1281
  function addCssOwner(pagesByCss, cssFile, page) {
1198
1282
  const owners = pagesByCss.get(cssFile) ?? /* @__PURE__ */ new Set();
1199
1283
  owners.add(page);