@dcoder-x/plugin-shared 0.1.12 → 0.1.13

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.
@@ -24,14 +24,21 @@ class ComponentExtractor {
24
24
  // parent route happened to import them first.
25
25
  const fileRouteIndex = new Map();
26
26
  for (const route of this.routes) {
27
- const relFwd = route.filePath.replace(/\\/g, '/');
28
- fileRouteIndex.set(relFwd, route.path);
29
- // Also index by just the basename-less relative path variations
30
27
  fileRouteIndex.set(route.filePath, route.path);
28
+ fileRouteIndex.set(route.filePath.replace(/\\/g, '/'), route.path);
29
+ }
30
+ // Stop the module walk at other route entry files — they will be
31
+ // processed in their own iteration with the correct route assigned.
32
+ const allRouteFiles = new Set();
33
+ for (const route of this.routes) {
34
+ allRouteFiles.add(route.filePath);
35
+ allRouteFiles.add(route.filePath.replace(/\\/g, '/'));
31
36
  }
32
37
  const seen = new Set(); // deduplicate across routes
33
38
  for (const route of this.routes) {
34
- const moduleFiles = this.getModuleFilesForRoute(route.filePath);
39
+ // The stop boundary excludes the current entry file itself (we need to visit it).
40
+ const stopAt = new Set([...allRouteFiles].filter((f) => f !== route.filePath && f !== route.filePath.replace(/\\/g, '/')));
41
+ const moduleFiles = this.getModuleFilesForRoute(route.filePath, stopAt);
35
42
  for (const filePath of moduleFiles) {
36
43
  if (seen.has(filePath))
37
44
  continue;
@@ -62,9 +69,15 @@ class ComponentExtractor {
62
69
  fileRouteIndex.set(route.filePath, route.path);
63
70
  fileRouteIndex.set(route.filePath.replace(/\\/g, '/'), route.path);
64
71
  }
72
+ const allRouteFiles = new Set();
73
+ for (const route of this.routes) {
74
+ allRouteFiles.add(route.filePath);
75
+ allRouteFiles.add(route.filePath.replace(/\\/g, '/'));
76
+ }
65
77
  const seen = new Set();
66
78
  for (const route of this.routes) {
67
- const moduleFiles = this.getModuleFilesForRoute(route.filePath);
79
+ const stopAt = new Set([...allRouteFiles].filter((f) => f !== route.filePath && f !== route.filePath.replace(/\\/g, '/')));
80
+ const moduleFiles = this.getModuleFilesForRoute(route.filePath, stopAt);
68
81
  for (const filePath of moduleFiles) {
69
82
  if (seen.has(filePath))
70
83
  continue;
@@ -121,15 +134,13 @@ class ComponentExtractor {
121
134
  }
122
135
  return components;
123
136
  }
124
- getModuleFilesForRoute(entryFilePath) {
137
+ getModuleFilesForRoute(entryFilePath, stopAt) {
125
138
  if (this.source.type === 'webpack') {
126
- return this.walkWebpackGraph(entryFilePath, this.source.compilation);
139
+ return this.walkWebpackGraph(entryFilePath, this.source.compilation, stopAt);
127
140
  }
128
- return this.walkRollupGraph(entryFilePath, this.source.moduleGraph);
141
+ return this.walkRollupGraph(entryFilePath, this.source.moduleGraph, stopAt);
129
142
  }
130
- walkWebpackGraph(entryFilePath, compilation) {
131
- // Normalise to relative forward-slash paths so visited keys and injectedMap
132
- // keys are consistent across macOS and Windows.
143
+ walkWebpackGraph(entryFilePath, compilation, stopAt) {
133
144
  const toRelFwd = (p) => path_1.default.isAbsolute(p)
134
145
  ? path_1.default.relative(process.cwd(), p).replace(/\\/g, '/')
135
146
  : p.replace(/\\/g, '/');
@@ -139,6 +150,9 @@ class ComponentExtractor {
139
150
  const current = queue.shift();
140
151
  if (visited.has(current))
141
152
  continue;
153
+ // Stop recursing into other route entry files — they own their own walk.
154
+ if (stopAt && current !== toRelFwd(entryFilePath) && (stopAt.has(current) || stopAt.has(path_1.default.resolve(process.cwd(), current))))
155
+ continue;
142
156
  visited.add(current);
143
157
  const mod = this.findWebpackModule(compilation, current);
144
158
  let queuedFromWebpackGraph = false;
@@ -150,10 +164,7 @@ class ComponentExtractor {
150
164
  }
151
165
  }
152
166
  }
153
- // Fallback for webpack variants where moduleGraph helpers are unavailable.
154
167
  if (!queuedFromWebpackGraph) {
155
- // getFileImportPaths resolves to absolute paths via resolveImportFile;
156
- // normalise them before queuing.
157
168
  for (const depPath of this.getFileImportPaths(current)) {
158
169
  if (depPath && !depPath.includes('node_modules')) {
159
170
  queue.push(toRelFwd(depPath));
@@ -266,24 +277,25 @@ class ComponentExtractor {
266
277
  }
267
278
  return resolved;
268
279
  }
269
- walkRollupGraph(entryFilePath, graph) {
270
- // Normalise the entry to a relative forward-slash path so it matches the
271
- // keys stored by ClippyVitePlugin (which also normalises to relative).
280
+ walkRollupGraph(entryFilePath, graph, stopAt) {
272
281
  const toRelFwd = (p) => path_1.default.isAbsolute(p)
273
282
  ? path_1.default.relative(process.cwd(), p).replace(/\\/g, '/')
274
283
  : p.replace(/\\/g, '/');
284
+ const entryKey = toRelFwd(entryFilePath);
275
285
  const visited = new Set();
276
- const queue = [toRelFwd(entryFilePath)];
286
+ const queue = [entryKey];
277
287
  while (queue.length > 0) {
278
288
  const current = queue.shift();
279
289
  if (visited.has(current))
280
290
  continue;
291
+ // Stop recursing into other route entry files — they own their own walk.
292
+ if (stopAt && current !== entryKey && (stopAt.has(current) || stopAt.has(path_1.default.resolve(process.cwd(), current))))
293
+ continue;
281
294
  visited.add(current);
282
295
  const mod = graph.get(current);
283
296
  if (!mod)
284
297
  continue;
285
298
  for (const importId of mod.importedIds) {
286
- // importedIds are already relative keys after the vite plugin normalised them
287
299
  if (importId && !importId.includes('node_modules')) {
288
300
  queue.push(toRelFwd(importId));
289
301
  }
@@ -194,16 +194,32 @@ class RouteExtractor {
194
194
  const expr = elementAttr.value?.expression;
195
195
  if (!expr)
196
196
  return null;
197
- // Try each JSX component name in document order; first match in the import map wins.
198
- // Wrapper components like <Can> or <RouteGuard> won't be in the map, so we fall
199
- // through to the actual page component nested inside them.
197
+ // Collect all JSX component names in document order (outermost first, then
198
+ // children). For each, resolve via the import map, then pick the best candidate:
199
+ // a file under pages/ beats a wrapper/provider/layout/guard/context.
200
200
  const componentNames = this.collectJSXComponentNames(expr);
201
+ const candidates = [];
201
202
  for (const componentName of componentNames) {
202
203
  const resolved = importMap.get(componentName);
203
204
  if (resolved)
204
- return resolved;
205
+ candidates.push(resolved);
205
206
  }
206
- return null;
207
+ if (candidates.length === 0)
208
+ return null;
209
+ // Score: page files rank highest, known non-page patterns rank lowest.
210
+ const score = (filePath) => {
211
+ const p = filePath.replace(/\\/g, '/').toLowerCase();
212
+ if (p.includes('/pages/'))
213
+ return 3;
214
+ if (p.includes('/views/') || p.includes('/screens/'))
215
+ return 2;
216
+ // Wrappers, providers, guards, layouts, contexts are structural — not page components
217
+ if (/\/(contexts?|providers?|guards?|layouts?|wrappers?|hoc|utils?|helpers?)\//.test(p) ||
218
+ /(provider|context|guard|layout|redirect|catchall|catch-all)/.test(p))
219
+ return 0;
220
+ return 1;
221
+ };
222
+ return candidates.reduce((best, c) => (score(c) >= score(best) ? c : best));
207
223
  }
208
224
  collectJSXComponentNames(node) {
209
225
  if (!node)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcoder-x/plugin-shared",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",