Skip to content

Commit

Permalink
Merge pull request #290 from Outblock:289-feature-analyze-imports-and…
Browse files Browse the repository at this point in the history
…-dependencies

Created some scripts to analyze
  • Loading branch information
tombeckenham authored Dec 16, 2024
2 parents 7765bc1 + 757c241 commit d296b71
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 0 deletions.
129 changes: 129 additions & 0 deletions build/analyze-dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const webpackConfig = require('../webpack.config.js');

class DependencyAnalyzer {
constructor() {
this.dependencies = {
background: new Set(),
ui: new Set(),
content: new Set(),
shared: new Set(),
};
}

analyzeDependencies(stats) {
const modules = new Map();

// Use compilation.modules instead of chunk.getModules()
stats.compilation.modules.forEach((module) => {
if (!module.resource || !module.resource.includes('node_modules')) {
return;
}

const pkg = module.resource.split('node_modules/')[1].split('/')[0];
const chunks = Array.from(module.chunksIterable || []);

chunks.forEach((chunk) => {
const target = chunk.name?.includes('background')
? 'background'
: chunk.name?.includes('ui')
? 'ui'
: chunk.name?.includes('content')
? 'content'
: null;

if (target) {
if (!modules.has(pkg)) {
modules.set(pkg, new Set());
}
modules.get(pkg).add(target);
}
});
});

// Categorize dependencies
modules.forEach((targets, pkg) => {
if (targets.size > 1) {
this.dependencies.shared.add(pkg);
} else {
const [target] = targets;
this.dependencies[target].add(pkg);
}
});

return this;
}

generateReport() {
let report = '# Extension Dependencies Analysis\n\n';
report += `> Generated on ${new Date().toLocaleString()}\n\n`;

// Add summary
report += '## Summary\n\n';
Object.entries(this.dependencies).forEach(([target, deps]) => {
report += `- **${target}**: ${deps.size} packages\n`;
});
report += '\n';

// Add detailed lists
Object.entries(this.dependencies).forEach(([target, deps]) => {
report += `## ${target.charAt(0).toUpperCase() + target.slice(1)} Dependencies\n\n`;
if (deps.size === 0) {
report += '_No dependencies_\n\n';
} else {
Array.from(deps)
.sort()
.forEach((dep) => {
report += `- \`${dep}\`\n`;
});
report += '\n';
}
});

return report;
}
}

// Prepare build environment
console.log('Preparing build environment...');

// Copy manifest
fs.copyFileSync(
path.join(__dirname, '../_raw/manifest/manifest.dev.json'),
path.join(__dirname, '../_raw/manifest.json')
);

// Clean dist directory
const distPath = path.join(__dirname, '../dist');
if (!fs.existsSync(distPath)) {
fs.mkdirSync(distPath);
} else {
fs.rmSync(distPath, { recursive: true });
fs.mkdirSync(distPath);
}

// Copy _raw contents to dist
fs.cpSync(path.join(__dirname, '../_raw'), distPath, { recursive: true });

// Get webpack config using the same configuration as build:dev
const config = webpackConfig({ config: 'dev' });

// Run the analysis
console.log('Starting webpack build and analysis...');
const analyzer = new DependencyAnalyzer();

webpack(config, (err, stats) => {
if (err || stats.hasErrors()) {
console.error('Build failed:', err || stats.toString());
process.exit(1);
}

console.log('Build complete, analyzing dependencies...');
const report = analyzer.analyzeDependencies(stats).generateReport();

const reportPath = path.join(__dirname, '../extension-dependencies.md');
fs.writeFileSync(reportPath, report);
console.log(`Analysis complete! Check ${reportPath}`);
});
136 changes: 136 additions & 0 deletions build/analyze-imports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;

class ImportAnalyzer {
constructor() {
this.imports = new Map(); // package -> Set of files using it
this.packageLocations = {
background: new Set(),
ui: new Set(),
content: new Set(),
};
}

analyzeFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const relativePath = path.relative(process.cwd(), filePath);

try {
const ast = parser.parse(content, {
sourceType: 'module',
plugins: ['typescript', 'jsx'],
});

traverse(ast, {
ImportDeclaration: (path) => {
const importPath = path.node.source.value;

// Only analyze external packages (not relative imports)
if (!importPath.startsWith('.') && !importPath.startsWith('@/')) {
const packageName = importPath.startsWith('@')
? importPath.split('/').slice(0, 2).join('/')
: importPath.split('/')[0];

if (!this.imports.has(packageName)) {
this.imports.set(packageName, new Set());
}
this.imports.get(packageName).add(relativePath);

// Categorize by location
if (relativePath.includes('background/')) {
this.packageLocations.background.add(packageName);
} else if (relativePath.includes('ui/')) {
this.packageLocations.ui.add(packageName);
} else if (relativePath.includes('content/')) {
this.packageLocations.content.add(packageName);
}
}
},
CallExpression(path) {
// Check for require() calls
if (path.node.callee.name === 'require') {
const arg = path.node.arguments[0];
if (arg && arg.type === 'StringLiteral') {
const importPath = arg.value;
if (!importPath.startsWith('.') && !importPath.startsWith('@/')) {
const packageName = importPath.startsWith('@')
? importPath.split('/').slice(0, 2).join('/')
: importPath.split('/')[0];

if (!this.imports.has(packageName)) {
this.imports.set(packageName, new Set());
}
this.imports.get(packageName).add(relativePath);

// Categorize by location
if (relativePath.includes('background/')) {
this.packageLocations.background.add(packageName);
} else if (relativePath.includes('ui/')) {
this.packageLocations.ui.add(packageName);
} else if (relativePath.includes('content/')) {
this.packageLocations.content.add(packageName);
}
}
}
}
},
});
} catch (error) {
console.warn(`Failed to parse ${relativePath}:`, error.message);
}
}

generateReport() {
let report = '# Dependency Usage Analysis\n\n';
report += `> Generated on ${new Date().toLocaleString()}\n\n`;

// Summary by location
report += '## Package Usage by Location\n\n';
Object.entries(this.packageLocations).forEach(([location, packages]) => {
report += `### ${location.charAt(0).toUpperCase() + location.slice(1)}\n\n`;
Array.from(packages)
.sort()
.forEach((pkg) => {
const usageCount = this.imports.get(pkg).size;
report += `- \`${pkg}\` (${usageCount} ${usageCount === 1 ? 'file' : 'files'})\n`;
});
report += '\n';
});

// Detailed usage
report += '## Detailed Package Usage\n\n';
Array.from(this.imports.entries())
.sort(([a], [b]) => a.localeCompare(b))
.forEach(([pkg, files]) => {
report += `### \`${pkg}\`\n\n`;
Array.from(files)
.sort()
.forEach((file) => {
report += `- ${file}\n`;
});
report += '\n';
});

return report;
}
}

// Run the analysis
console.log('Starting import analysis...');
const analyzer = new ImportAnalyzer();

// Find all TypeScript and JavaScript files
const files = glob.sync('src/**/*.{ts,tsx,js,jsx}', {
ignore: ['**/node_modules/**', '**/dist/**'],
});

files.forEach((file) => {
analyzer.analyzeFile(file);
});

const report = analyzer.generateReport();
fs.writeFileSync('dependency-usage.md', report);
console.log('Analysis complete! Check dependency-usage.md');

0 comments on commit d296b71

Please sign in to comment.