uniapp-cross-package-analyzer 1.0.0 → 1.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniapp-cross-package-analyzer",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "UniApp 跨包依赖分析 Webpack 插件,可视化分析分包之间的依赖关系",
5
5
  "main": "src/index.js",
6
6
  "keywords": [
package/src/index.js CHANGED
@@ -124,6 +124,69 @@ class UniAppCrossPackageAnalyzer {
124
124
  const nodeMap = new Map();
125
125
  const linkSet = new Set();
126
126
 
127
+ // 缓存文件内容用于读取代码行
128
+ const fileContentCache = {};
129
+ const getFileLines = (filePath) => {
130
+ try {
131
+ const fullPath = path.join(inputPath, filePath);
132
+ if (!fileContentCache[fullPath]) {
133
+ if (fs.existsSync(fullPath)) {
134
+ fileContentCache[fullPath] = fs.readFileSync(fullPath, 'utf8').split('\n');
135
+ } else {
136
+ fileContentCache[fullPath] = null;
137
+ }
138
+ }
139
+ return fileContentCache[fullPath];
140
+ } catch (e) {}
141
+ return null;
142
+ };
143
+
144
+ const getLineContent = (filePath, lineNum) => {
145
+ if (typeof lineNum !== 'number' || lineNum < 1) return null;
146
+ const lines = getFileLines(filePath);
147
+ if (lines && lines[lineNum - 1] !== undefined) {
148
+ return lines[lineNum - 1].trim();
149
+ }
150
+ return null;
151
+ };
152
+
153
+ // 在源文件中搜索 import 语句的实际行号
154
+ const findImportLine = (sourcePath, request, targetPath) => {
155
+ const lines = getFileLines(sourcePath);
156
+ if (!lines || !request) return null;
157
+
158
+ // 清理 request,去掉查询参数
159
+ const cleanRequest = request.replace(/\?.*$/, '');
160
+
161
+ // 从 targetPath 提取更精确的匹配关键词
162
+ // 例如 /shares/subMain/services/visitCard/index.js -> visitCard/index 或 visitCard
163
+ const targetParts = targetPath.replace(/^\//, '').split('/');
164
+ const targetFileName = targetParts[targetParts.length - 1].replace(/\.(js|ts|vue|json)$/, '');
165
+ const targetDir = targetParts.length >= 2 ? targetParts[targetParts.length - 2] : '';
166
+
167
+ // 构建多个匹配模式,按优先级排序
168
+ const patterns = [
169
+ cleanRequest, // 完整的 request 路径
170
+ targetDir && targetFileName === 'index' ? targetDir : null, // 如果是 index.js,用目录名匹配
171
+ targetDir ? `${targetDir}/${targetFileName}` : null, // 目录/文件名
172
+ targetDir ? `${targetDir}/index` : null, // 目录/index
173
+ ].filter(Boolean);
174
+
175
+ for (let i = 0; i < lines.length; i++) {
176
+ const line = lines[i];
177
+ // 必须是 import/require 语句
178
+ if (!line.includes('import ') && !line.includes('require(')) continue;
179
+
180
+ // 按优先级匹配
181
+ for (const pattern of patterns) {
182
+ if (line.includes(pattern)) {
183
+ return { line: i + 1, code: line.trim() };
184
+ }
185
+ }
186
+ }
187
+ return null;
188
+ };
189
+
127
190
  // 第一遍:收集所有模块
128
191
  for (const module of modules) {
129
192
  if (!module.resource || !module.resource.includes(inputPath)) continue;
@@ -191,7 +254,19 @@ class UniAppCrossPackageAnalyzer {
191
254
  if (!linkSet.has(linkKey)) {
192
255
  linkSet.add(linkKey);
193
256
 
194
- const location = this.getImportLocation(dep);
257
+ // 优先通过搜索源文件找到 import 语句的真实位置
258
+ const importInfo = findImportLine(sourcePath, dep.request || dep.userRequest, targetPath);
259
+ let location, code;
260
+
261
+ if (importInfo) {
262
+ location = { line: importInfo.line, column: 0 };
263
+ code = importInfo.code;
264
+ } else {
265
+ // 回退到 dep.loc(可能不准确)
266
+ location = this.getImportLocation(dep, module);
267
+ code = getLineContent(sourcePath, location.line);
268
+ }
269
+
195
270
  const depInfo = {
196
271
  source: sourcePath,
197
272
  target: targetPath,
@@ -199,6 +274,7 @@ class UniAppCrossPackageAnalyzer {
199
274
  targetPackage: targetPackage,
200
275
  size: targetNode.size,
201
276
  location: location,
277
+ code: code,
202
278
  request: dep.request || dep.userRequest || targetPath
203
279
  };
204
280
 
@@ -252,8 +328,10 @@ class UniAppCrossPackageAnalyzer {
252
328
  return 0;
253
329
  }
254
330
 
255
- getImportLocation(dep) {
331
+ getImportLocation(dep, sourceModule) {
256
332
  let line = '?', column = '?';
333
+
334
+ // 优先从依赖对象本身获取位置(import 语句在源文件中的位置)
257
335
  if (dep.loc) {
258
336
  if (dep.loc.start) {
259
337
  line = dep.loc.start.line;
@@ -263,6 +341,24 @@ class UniAppCrossPackageAnalyzer {
263
341
  column = dep.loc.column || 0;
264
342
  }
265
343
  }
344
+
345
+ // 如果 dep.loc 不可用,尝试从源模块的依赖信息中获取
346
+ if (line === '?' && sourceModule && sourceModule.blocks) {
347
+ for (const block of sourceModule.blocks) {
348
+ if (block.dependencies) {
349
+ for (const blockDep of block.dependencies) {
350
+ if (blockDep === dep && blockDep.loc) {
351
+ if (blockDep.loc.start) {
352
+ line = blockDep.loc.start.line;
353
+ column = blockDep.loc.start.column;
354
+ }
355
+ break;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+
266
362
  return { line, column };
267
363
  }
268
364
 
@@ -280,10 +376,10 @@ class UniAppCrossPackageAnalyzer {
280
376
 
281
377
  const reportData = this.prepareReportData();
282
378
 
379
+ // 始终导出详细的 JSON 报告(包含每行跨包引用点位)
380
+ this.exportDetailedJson(reportData, outputPath);
381
+
283
382
  if (this.analyzerMode === 'json') {
284
- const jsonPath = path.join(outputPath, this.statsFilename);
285
- fs.writeFileSync(jsonPath, JSON.stringify(reportData, null, 2));
286
- console.log(`[UniAppCrossPackageAnalyzer] JSON 报告已生成: ${jsonPath}`);
287
383
  return;
288
384
  }
289
385
 
@@ -299,6 +395,34 @@ class UniAppCrossPackageAnalyzer {
299
395
  this.startServer(html);
300
396
  }
301
397
 
398
+ exportDetailedJson(reportData, outputPath) {
399
+ // 简化的跨包引用报告
400
+ const crossRefs = [];
401
+
402
+ // 只保留分包跨分包的调用
403
+ for (const dep of reportData.crossDeps) {
404
+ if (dep.sourcePackage === 'main' || dep.targetPackage === 'main') {
405
+ continue;
406
+ }
407
+
408
+ crossRefs.push({
409
+ from: dep.source,
410
+ to: dep.target,
411
+ pkg: `${dep.sourcePackage} -> ${dep.targetPackage}`,
412
+ line: dep.location.line
413
+ });
414
+ }
415
+
416
+ const result = {
417
+ total: crossRefs.length,
418
+ refs: crossRefs
419
+ };
420
+
421
+ const jsonPath = path.join(outputPath, this.statsFilename);
422
+ fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2));
423
+ console.log(`[UniAppCrossPackageAnalyzer] JSON 报告已生成: ${jsonPath}`);
424
+ }
425
+
302
426
  prepareReportData() {
303
427
  const crossDepsByPackage = {};
304
428
 
package/src/template.js CHANGED
@@ -162,7 +162,7 @@ module.exports = function generateHTML(data, formatSize) {
162
162
  <div class="table-container">
163
163
  <table>
164
164
  <thead>
165
- <tr><th>源文件</th><th>源分包</th><th>目标文件</th><th>目标分包</th><th>体积</th><th>行号</th></tr>
165
+ <tr><th>源文件</th><th>源分包</th><th>目标文件</th><th>目标分包</th><th>体积</th><th>行号</th><th>代码</th></tr>
166
166
  </thead>
167
167
  <tbody id="depTableBody"></tbody>
168
168
  </table>
@@ -282,10 +282,14 @@ module.exports = function generateHTML(data, formatSize) {
282
282
  '<span class="file-collapse-icon" style="display: inline-block; width: 16px; font-size: 10px;">▶</span>' +
283
283
  '<span style="color: #aaa; font-size: 12px;">' + file + '</span>' +
284
284
  '<span class="badge" style="background: #667eea;">' + fileDeps.length + '</span></div>' +
285
- '<div id="' + fileCollapseId + '" style="display: none; margin-top: 8px; margin-left: 20px; font-size: 11px; color: #667eea;">';
285
+ '<div id="' + fileCollapseId + '" style="display: none; margin-top: 8px; margin-left: 20px; font-size: 11px;">';
286
286
  fileDeps.forEach(dep => {
287
287
  const targetFile = depDirection === 'imports' ? dep.target : dep.source;
288
- html += '<div style="padding: 4px 0;">→ ' + targetFile + ' <span class="size">(' + formatSize(dep.size) + ')</span> L' + dep.location.line + '</div>';
288
+ const codeSnippet = dep.code ? ('<div style="background: #0d0d15; padding: 6px 10px; border-radius: 4px; margin-top: 4px; font-family: monospace; color: #e0e0e0; white-space: pre-wrap; word-break: break-all; border-left: 3px solid #667eea;">' + escapeHtml(dep.code) + '</div>') : '';
289
+ html += '<div style="padding: 8px 0; border-bottom: 1px dotted #2a2a3e;">' +
290
+ '<div style="color: #667eea;">→ ' + targetFile + ' <span class="size">(' + formatSize(dep.size) + ')</span> <span style="color: #888;">L' + dep.location.line + '</span></div>' +
291
+ codeSnippet +
292
+ '</div>';
289
293
  });
290
294
  html += '</div></div>';
291
295
  });
@@ -417,12 +421,15 @@ module.exports = function generateHTML(data, formatSize) {
417
421
  if (filter.search) { const s = filter.search.toLowerCase(); deps = deps.filter(d => d.source.toLowerCase().includes(s) || d.target.toLowerCase().includes(s)); }
418
422
  tbody.innerHTML = deps.map(dep => {
419
423
  const isMainS = dep.sourcePackage === 'main', isMainT = dep.targetPackage === 'main';
420
- return '<tr><td class="path">' + dep.source + '</td><td><span class="tag ' + (isMainS ? 'main' : '') + '">' + (isMainS ? '主包' : dep.sourcePackage) + '</span></td><td class="path">' + dep.target + '</td><td><span class="tag ' + (isMainT ? 'main' : '') + '">' + (isMainT ? '主包' : dep.targetPackage) + '</span></td><td class="size">' + formatSize(dep.size) + '</td><td>L' + dep.location.line + '</td></tr>';
421
- }).join('') || '<tr><td colspan="6" style="text-align: center; color: #666;">无匹配数据</td></tr>';
424
+ const codeCell = dep.code ? '<code style="background: #0d0d15; padding: 2px 6px; border-radius: 3px; font-size: 11px; color: #e0e0e0;">' + escapeHtml(dep.code) + '</code>' : '-';
425
+ return '<tr><td class="path">' + dep.source + '</td><td><span class="tag ' + (isMainS ? 'main' : '') + '">' + (isMainS ? '主包' : dep.sourcePackage) + '</span></td><td class="path">' + dep.target + '</td><td><span class="tag ' + (isMainT ? 'main' : '') + '">' + (isMainT ? '主包' : dep.targetPackage) + '</span></td><td class="size">' + formatSize(dep.size) + '</td><td>L' + dep.location.line + '</td><td style="max-width: 300px; overflow: hidden; text-overflow: ellipsis;">' + codeCell + '</td></tr>';
426
+ }).join('') || '<tr><td colspan="7" style="text-align: center; color: #666;">无匹配数据</td></tr>';
422
427
  }
423
428
 
424
429
  function formatSize(bytes) { if (!bytes || bytes === 0) return '0 B'; if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; }
425
430
 
431
+ function escapeHtml(str) { if (!str) return ''; return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;'); }
432
+
426
433
  document.getElementById('sourceFilter').addEventListener('change', updateFilter);
427
434
  document.getElementById('targetFilter').addEventListener('change', updateFilter);
428
435
  document.getElementById('searchInput').addEventListener('input', updateFilter);