treeshake-check 1.0.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/bin/treeshake-check.js +3 -0
  4. package/dist/analyzers/barrel.d.ts +6 -0
  5. package/dist/analyzers/barrel.d.ts.map +1 -0
  6. package/dist/analyzers/barrel.js +124 -0
  7. package/dist/analyzers/barrel.js.map +1 -0
  8. package/dist/analyzers/exports.d.ts +6 -0
  9. package/dist/analyzers/exports.d.ts.map +1 -0
  10. package/dist/analyzers/exports.js +192 -0
  11. package/dist/analyzers/exports.js.map +1 -0
  12. package/dist/analyzers/module-type.d.ts +6 -0
  13. package/dist/analyzers/module-type.d.ts.map +1 -0
  14. package/dist/analyzers/module-type.js +163 -0
  15. package/dist/analyzers/module-type.js.map +1 -0
  16. package/dist/analyzers/side-effects.d.ts +10 -0
  17. package/dist/analyzers/side-effects.d.ts.map +1 -0
  18. package/dist/analyzers/side-effects.js +203 -0
  19. package/dist/analyzers/side-effects.js.map +1 -0
  20. package/dist/bundlers/detector.d.ts +14 -0
  21. package/dist/bundlers/detector.d.ts.map +1 -0
  22. package/dist/bundlers/detector.js +100 -0
  23. package/dist/bundlers/detector.js.map +1 -0
  24. package/dist/bundlers/esbuild.d.ts +6 -0
  25. package/dist/bundlers/esbuild.d.ts.map +1 -0
  26. package/dist/bundlers/esbuild.js +142 -0
  27. package/dist/bundlers/esbuild.js.map +1 -0
  28. package/dist/bundlers/vite.d.ts +7 -0
  29. package/dist/bundlers/vite.d.ts.map +1 -0
  30. package/dist/bundlers/vite.js +195 -0
  31. package/dist/bundlers/vite.js.map +1 -0
  32. package/dist/bundlers/webpack.d.ts +6 -0
  33. package/dist/bundlers/webpack.d.ts.map +1 -0
  34. package/dist/bundlers/webpack.js +128 -0
  35. package/dist/bundlers/webpack.js.map +1 -0
  36. package/dist/cli/commands.d.ts +6 -0
  37. package/dist/cli/commands.d.ts.map +1 -0
  38. package/dist/cli/commands.js +116 -0
  39. package/dist/cli/commands.js.map +1 -0
  40. package/dist/index.d.ts +3 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +189 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/types/index.d.ts +152 -0
  45. package/dist/types/index.d.ts.map +1 -0
  46. package/dist/types/index.js +5 -0
  47. package/dist/types/index.js.map +1 -0
  48. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # treeshake-check
2
+
3
+ A production-grade CLI tool to analyze JavaScript/TypeScript React applications for tree-shaking issues.
4
+
5
+ ## Features
6
+
7
+ - 🔍 **Auto-detect bundlers** - Vite, Webpack, Rollup, esbuild
8
+ - 🌳 **Barrel file analysis** - Detect `export *` patterns that break tree-shaking
9
+ - ⚠️ **Side effects detection** - Find top-level code that prevents dead code elimination
10
+ - 📦 **CommonJS detection** - Identify modules that don't tree-shake
11
+ - 📊 **Bundle stats parsing** - Analyze webpack stats.json, esbuild metafile, Vite/Rollup output
12
+ - 💡 **Fix suggestions** - Actionable recommendations for each issue
13
+ - 📋 **Multiple output formats** - Human-readable text or JSON for CI
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g treeshake-check
19
+
20
+ # Or use directly with npx
21
+ npx treeshake-check analyze
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Analyze a project
27
+
28
+ ```bash
29
+ # Analyze current directory (auto-detect bundler)
30
+ treeshake-check analyze
31
+
32
+ # Analyze a specific directory
33
+ treeshake-check analyze ./my-react-app
34
+
35
+ # With specific bundler stats
36
+ treeshake-check analyze --stats dist/stats.json
37
+ ```
38
+
39
+ ### CLI Options
40
+
41
+ ```bash
42
+ treeshake-check analyze [path] [options]
43
+
44
+ Options:
45
+ -b, --bundler <type> Force bundler type (vite|webpack|rollup|esbuild)
46
+ -s, --stats <file> Path to bundle stats/metafile
47
+ -o, --output <format> Output format (text|json) (default: "text")
48
+ -t, --threshold <bytes> Minimum size to report (default: "0")
49
+ --no-suggestions Hide fix suggestions
50
+ -i, --include <patterns> Glob patterns to include
51
+ -e, --exclude <patterns> Glob patterns to exclude
52
+ ```
53
+
54
+ ### Generate bundle stats
55
+
56
+ #### Webpack
57
+
58
+ ```bash
59
+ webpack --json > stats.json
60
+ treeshake-check analyze --stats stats.json
61
+ ```
62
+
63
+ #### Vite/Rollup
64
+
65
+ ```bash
66
+ # Install rollup-plugin-visualizer
67
+ npm install -D rollup-plugin-visualizer
68
+
69
+ # Configure in vite.config.ts
70
+ import { visualizer } from 'rollup-plugin-visualizer';
71
+
72
+ export default defineConfig({
73
+ plugins: [
74
+ visualizer({ filename: 'stats.json', json: true })
75
+ ]
76
+ });
77
+ ```
78
+
79
+ #### esbuild
80
+
81
+ ```javascript
82
+ // Enable metafile in build
83
+ await esbuild
84
+ .build({
85
+ entryPoints: ["src/index.ts"],
86
+ bundle: true,
87
+ metafile: true,
88
+ outfile: "dist/bundle.js",
89
+ })
90
+ .then((result) => {
91
+ fs.writeFileSync("meta.json", JSON.stringify(result.metafile));
92
+ });
93
+ ```
94
+
95
+ ## Example Output
96
+
97
+ ```
98
+ 🔍 Tree-Shaking Analysis Report
99
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
100
+
101
+ Summary:
102
+ 📁 Project: /path/to/project
103
+ 🔧 Bundler: vite
104
+ 📊 Files Analyzed: 42
105
+
106
+ ⚠️ Issues Found: 5
107
+ ● Critical: 1
108
+ ● High: 2
109
+ ● Medium: 2
110
+ 📦 Potential Savings: 85.3 KB
111
+
112
+ CRITICAL ISSUES
113
+ ────────────────
114
+ 1. CommonJS Module: node_modules/lodash/index.js
115
+ └─ Pattern: require('lodash')
116
+ └─ Impact: 72 KB
117
+ └─ lodash CommonJS bundle is included. Use lodash-es for tree-shaking.
118
+ └─ Fix: Switch to lodash-es
119
+ import { debounce } from 'lodash-es'
120
+
121
+ HIGH PRIORITY ISSUES
122
+ ────────────────────
123
+ 2. Wildcard Re-export: src/components/index.ts
124
+ └─ Line: 5
125
+ └─ Pattern: export * from './Button'
126
+ └─ Impact: 8.2 KB
127
+ └─ Wildcard re-export prevents tree-shaking.
128
+ └─ Fix: Use explicit named exports
129
+ export { Button } from './Button'
130
+ ```
131
+
132
+ ## JSON Output
133
+
134
+ Use `--output json` for CI integration:
135
+
136
+ ```json
137
+ {
138
+ "summary": {
139
+ "projectPath": "/path/to/project",
140
+ "bundler": "vite",
141
+ "totalIssues": 5,
142
+ "criticalCount": 1,
143
+ "highCount": 2,
144
+ "mediumCount": 2,
145
+ "lowCount": 0,
146
+ "estimatedSavings": 87347,
147
+ "analyzedFiles": 42
148
+ },
149
+ "issues": [
150
+ {
151
+ "type": "commonjs-module",
152
+ "severity": "critical",
153
+ "file": "node_modules/lodash/index.js",
154
+ "pattern": "require('lodash')",
155
+ "estimatedImpact": 72000,
156
+ "suggestion": {
157
+ "title": "Switch to lodash-es",
158
+ "code": "import { debounce } from 'lodash-es'"
159
+ }
160
+ }
161
+ ]
162
+ }
163
+ ```
164
+
165
+ ## Issue Types
166
+
167
+ | Type | Description |
168
+ | ---------------------------- | -------------------------------------------- |
169
+ | `barrel-file` | Barrel files with many re-exports |
170
+ | `wildcard-reexport` | `export *` patterns that include all exports |
171
+ | `side-effect` | Top-level code that runs on import |
172
+ | `commonjs-module` | CommonJS modules that don't tree-shake |
173
+ | `unused-export` | Exports not imported anywhere |
174
+ | `missing-sideeffects-config` | Missing `sideEffects` in package.json |
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import "../dist/index.js";
@@ -0,0 +1,6 @@
1
+ import type { TreeShakingIssue } from "../types/index.js";
2
+ /**
3
+ * Analyzes files for barrel file patterns that can break tree-shaking
4
+ */
5
+ export declare function analyzeBarrelFiles(files: string[], projectPath: string): Promise<TreeShakingIssue[]>;
6
+ //# sourceMappingURL=barrel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barrel.d.ts","sourceRoot":"","sources":["../../src/analyzers/barrel.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,mBAAmB,CAAC;AAEzE;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAoB7B"}
@@ -0,0 +1,124 @@
1
+ import * as acorn from "acorn";
2
+ import * as walk from "acorn-walk";
3
+ import { readFileSync } from "fs";
4
+ import { basename, relative } from "path";
5
+ /**
6
+ * Analyzes files for barrel file patterns that can break tree-shaking
7
+ */
8
+ export async function analyzeBarrelFiles(files, projectPath) {
9
+ const issues = [];
10
+ for (const file of files) {
11
+ const fileName = basename(file);
12
+ // Only check index files (common barrel file pattern)
13
+ if (!fileName.match(/^index\.(js|ts|jsx|tsx|mjs|mts)$/)) {
14
+ continue;
15
+ }
16
+ try {
17
+ const content = readFileSync(file, "utf-8");
18
+ const fileIssues = analyzeBarrelContent(content, file, projectPath);
19
+ issues.push(...fileIssues);
20
+ }
21
+ catch {
22
+ // Skip files that can't be read
23
+ }
24
+ }
25
+ return issues;
26
+ }
27
+ function analyzeBarrelContent(content, filePath, projectPath) {
28
+ const issues = [];
29
+ const relPath = relative(projectPath, filePath);
30
+ // Count re-exports to determine if this is a barrel file
31
+ let reexportCount = 0;
32
+ let wildcardReexportCount = 0;
33
+ const wildcardSources = [];
34
+ try {
35
+ const ast = acorn.parse(content, {
36
+ ecmaVersion: "latest",
37
+ sourceType: "module",
38
+ locations: true,
39
+ });
40
+ walk.simple(ast, {
41
+ ExportAllDeclaration(node) {
42
+ wildcardReexportCount++;
43
+ reexportCount++;
44
+ wildcardSources.push({
45
+ source: node.source?.value || "unknown",
46
+ line: node.loc?.start?.line || 0,
47
+ });
48
+ },
49
+ ExportNamedDeclaration(node) {
50
+ if (node.source) {
51
+ reexportCount++;
52
+ }
53
+ },
54
+ });
55
+ }
56
+ catch {
57
+ // Fall back to regex-based detection for files that fail to parse
58
+ const wildcardMatches = content.matchAll(/export\s+\*\s+from\s+['"]([^'"]+)['"]/g);
59
+ for (const match of wildcardMatches) {
60
+ wildcardReexportCount++;
61
+ reexportCount++;
62
+ const line = content.substring(0, match.index).split("\n").length;
63
+ wildcardSources.push({ source: match[1], line });
64
+ }
65
+ const namedReexportMatches = content.matchAll(/export\s+\{[^}]+\}\s+from\s+['"][^'"]+['"]/g);
66
+ for (const _match of namedReexportMatches) {
67
+ reexportCount++;
68
+ }
69
+ }
70
+ // Only warn about barrel files with wildcard re-exports
71
+ if (wildcardReexportCount > 0) {
72
+ for (const { source, line } of wildcardSources) {
73
+ const severity = wildcardReexportCount > 3 ? "critical" : "high";
74
+ issues.push({
75
+ type: "wildcard-reexport",
76
+ severity,
77
+ file: relPath,
78
+ line,
79
+ pattern: `export * from '${source}'`,
80
+ description: `Wildcard re-export prevents tree-shaking. All exports from '${source}' will be included even if unused.`,
81
+ estimatedImpact: estimateWildcardImpact(source),
82
+ suggestion: {
83
+ title: "Use explicit named exports",
84
+ description: "Replace wildcard re-exports with explicit named exports to enable tree-shaking.",
85
+ code: `export { SpecificExport } from '${source}'`,
86
+ },
87
+ });
88
+ }
89
+ }
90
+ // Warn about barrel files in general if they have many re-exports
91
+ if (reexportCount > 5 && wildcardReexportCount === 0) {
92
+ issues.push({
93
+ type: "barrel-file",
94
+ severity: "medium",
95
+ file: relPath,
96
+ pattern: `${reexportCount} re-exports in barrel file`,
97
+ description: `Large barrel file with ${reexportCount} re-exports. Some bundlers may include all modules when importing from this barrel.`,
98
+ estimatedImpact: reexportCount * 500, // Rough estimate
99
+ suggestion: {
100
+ title: "Consider direct imports",
101
+ description: "Import directly from source modules instead of through the barrel file.",
102
+ code: `import { Component } from './components/Component' // instead of './components'`,
103
+ },
104
+ });
105
+ }
106
+ return issues;
107
+ }
108
+ /**
109
+ * Estimate the bundle size impact of a wildcard re-export
110
+ */
111
+ function estimateWildcardImpact(source) {
112
+ // Rough heuristics based on common patterns
113
+ if (source.includes("lodash"))
114
+ return 25000;
115
+ if (source.includes("icons") || source.includes("Icon"))
116
+ return 50000;
117
+ if (source.includes("utils") || source.includes("helpers"))
118
+ return 5000;
119
+ if (source.includes("components"))
120
+ return 10000;
121
+ // Default estimate
122
+ return 3000;
123
+ }
124
+ //# sourceMappingURL=barrel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barrel.js","sourceRoot":"","sources":["../../src/analyzers/barrel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { TreeShakingIssue } from "../types/index.js";
2
+ /**
3
+ * Analyze exports to find unused ones
4
+ */
5
+ export declare function analyzeUnusedExports(files: string[], projectPath: string): Promise<TreeShakingIssue[]>;
6
+ //# sourceMappingURL=exports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../../src/analyzers/exports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAGjB,MAAM,mBAAmB,CAAC;AAO3B;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiC7B"}
@@ -0,0 +1,192 @@
1
+ import * as acorn from "acorn";
2
+ import * as walk from "acorn-walk";
3
+ import { readFileSync } from "fs";
4
+ import { relative, dirname, resolve } from "path";
5
+ /**
6
+ * Analyze exports to find unused ones
7
+ */
8
+ export async function analyzeUnusedExports(files, projectPath) {
9
+ const issues = [];
10
+ // Build complete export/import graph
11
+ const graph = buildExportGraph(files, projectPath);
12
+ // Find exports that are never imported
13
+ const unusedExports = findUnusedExports(graph, files, projectPath);
14
+ for (const { file, exportName, line } of unusedExports) {
15
+ const relPath = relative(projectPath, file);
16
+ // Skip if it's a default export from an entry point
17
+ if (isEntryPoint(file, projectPath)) {
18
+ continue;
19
+ }
20
+ issues.push({
21
+ type: "unused-export",
22
+ severity: "low",
23
+ file: relPath,
24
+ line,
25
+ pattern: `export ${exportName}`,
26
+ description: `'${exportName}' is exported but never imported in the analyzed codebase.`,
27
+ estimatedImpact: 500,
28
+ suggestion: {
29
+ title: "Remove unused export",
30
+ description: `If '${exportName}' is not used externally, consider removing it or marking it as internal.`,
31
+ },
32
+ });
33
+ }
34
+ return issues;
35
+ }
36
+ function buildExportGraph(files, projectPath) {
37
+ const exports = new Map();
38
+ const imports = new Map();
39
+ for (const file of files) {
40
+ try {
41
+ const content = readFileSync(file, "utf-8");
42
+ const fileExports = new Set();
43
+ const fileImports = new Map();
44
+ const ast = acorn.parse(content, {
45
+ ecmaVersion: "latest",
46
+ sourceType: "module",
47
+ locations: true,
48
+ });
49
+ walk.simple(ast, {
50
+ ExportNamedDeclaration(node) {
51
+ // export { a, b }
52
+ if (node.specifiers) {
53
+ for (const spec of node.specifiers) {
54
+ const exportedName = spec.exported?.name || spec.local?.name;
55
+ if (exportedName) {
56
+ fileExports.add(exportedName);
57
+ }
58
+ }
59
+ }
60
+ // export const/let/var/function/class
61
+ if (node.declaration) {
62
+ if (node.declaration.declarations) {
63
+ for (const decl of node.declaration.declarations) {
64
+ if (decl.id?.name) {
65
+ fileExports.add(decl.id.name);
66
+ }
67
+ }
68
+ }
69
+ else if (node.declaration.id?.name) {
70
+ fileExports.add(node.declaration.id.name);
71
+ }
72
+ }
73
+ },
74
+ ExportDefaultDeclaration() {
75
+ fileExports.add("default");
76
+ },
77
+ ExportAllDeclaration(node) {
78
+ // export * from "..."
79
+ if (node.source?.value) {
80
+ fileExports.add(`* from ${node.source.value}`);
81
+ }
82
+ },
83
+ ImportDeclaration(node) {
84
+ if (!node.source?.value)
85
+ return;
86
+ const sourcePath = node.source.value;
87
+ for (const spec of node.specifiers || []) {
88
+ if (spec.type === "ImportSpecifier") {
89
+ const importedName = spec.imported?.name;
90
+ if (importedName) {
91
+ fileImports.set(importedName, resolveImportPath(file, sourcePath));
92
+ }
93
+ }
94
+ else if (spec.type === "ImportDefaultSpecifier") {
95
+ fileImports.set("default", resolveImportPath(file, sourcePath));
96
+ }
97
+ else if (spec.type === "ImportNamespaceSpecifier") {
98
+ fileImports.set("*", resolveImportPath(file, sourcePath));
99
+ }
100
+ }
101
+ },
102
+ });
103
+ exports.set(file, fileExports);
104
+ imports.set(file, fileImports);
105
+ }
106
+ catch {
107
+ // Skip files that can't be parsed
108
+ }
109
+ }
110
+ return { exports, imports };
111
+ }
112
+ function findUnusedExports(graph, files, projectPath) {
113
+ const unusedExports = [];
114
+ // Collect all imported names across the codebase
115
+ const allImports = new Map(); // source file -> imported names
116
+ for (const [importingFile, fileImports] of graph.imports) {
117
+ for (const [importedName, sourceFile] of fileImports) {
118
+ if (!allImports.has(sourceFile)) {
119
+ allImports.set(sourceFile, new Set());
120
+ }
121
+ allImports.get(sourceFile).add(importedName);
122
+ }
123
+ }
124
+ // Check each export against imports
125
+ for (const [file, fileExports] of graph.exports) {
126
+ const usedInFile = allImports.get(file) || new Set();
127
+ for (const exportName of fileExports) {
128
+ // Skip wildcard re-exports
129
+ if (exportName.startsWith("*"))
130
+ continue;
131
+ // Check if this export is used
132
+ const isUsed = usedInFile.has(exportName) ||
133
+ usedInFile.has("*") || // namespace import uses all
134
+ (exportName === "default" && usedInFile.has("default"));
135
+ if (!isUsed) {
136
+ unusedExports.push({
137
+ file,
138
+ exportName,
139
+ line: getExportLine(file, exportName),
140
+ });
141
+ }
142
+ }
143
+ }
144
+ return unusedExports;
145
+ }
146
+ function resolveImportPath(fromFile, importPath) {
147
+ // Handle relative imports
148
+ if (importPath.startsWith(".")) {
149
+ const dir = dirname(fromFile);
150
+ let resolved = resolve(dir, importPath);
151
+ // Add common extensions if needed
152
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts", ""];
153
+ for (const ext of extensions) {
154
+ if (resolved.endsWith(ext)) {
155
+ return resolved;
156
+ }
157
+ }
158
+ // Try adding extensions
159
+ return resolved;
160
+ }
161
+ // For node_modules, return as-is
162
+ return importPath;
163
+ }
164
+ function isEntryPoint(file, projectPath) {
165
+ const relPath = relative(projectPath, file).toLowerCase();
166
+ // Common entry point patterns
167
+ const entryPatterns = [
168
+ /^src\/index\./,
169
+ /^src\/main\./,
170
+ /^src\/app\./,
171
+ /^index\./,
172
+ /^main\./,
173
+ ];
174
+ return entryPatterns.some((pattern) => pattern.test(relPath));
175
+ }
176
+ function getExportLine(file, exportName) {
177
+ try {
178
+ const content = readFileSync(file, "utf-8");
179
+ const lines = content.split("\n");
180
+ for (let i = 0; i < lines.length; i++) {
181
+ const line = lines[i];
182
+ if (line.includes(`export`) && line.includes(exportName)) {
183
+ return i + 1;
184
+ }
185
+ }
186
+ }
187
+ catch {
188
+ // Ignore
189
+ }
190
+ return 0;
191
+ }
192
+ //# sourceMappingURL=exports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exports.js","sourceRoot":"","sources":["../../src/analyzers/exports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { TreeShakingIssue } from "../types/index.js";
2
+ /**
3
+ * Analyze files to detect CommonJS vs ESM module patterns
4
+ */
5
+ export declare function analyzeModuleTypes(files: string[], projectPath: string): Promise<TreeShakingIssue[]>;
6
+ //# sourceMappingURL=module-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-type.d.ts","sourceRoot":"","sources":["../../src/analyzers/module-type.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAoB1D;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EAAE,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAc7B"}