11#!/usr/bin/env node
22
3- const fs = require ( 'fs' )
4- const path = require ( 'path' )
5-
6- const BARREL_FILE_PATTERNS = [ 'index.ts' , 'index.js' , 'index.tsx' , 'index.jsx' ]
3+ /**
4+ * Detects barrel files that hurt build performance and tree-shaking
5+ * Prevents re-export patterns that slow down bundlers
6+ */
7+
8+ // ============================================================================
9+ // CONFIGURATION - Customize these settings for your project
10+ // ============================================================================
11+
12+ // File names that are considered potential barrel files
13+ const BARREL_FILE_PATTERNS = [
14+ 'index.ts' ,
15+ 'index.js' ,
16+ 'index.mjs' ,
17+ 'index.cjs' ,
18+ 'index.tsx' ,
19+ 'index.jsx'
20+ ]
721
8- // Directories to exclude from barrel file checks
9- const EXCLUDED_DIRS = [
22+ // Directories to exclude from barrel file checking
23+ const EXCLUDED_DIRECTORIES = [
1024 'node_modules' ,
1125 '.next' ,
12- '.git' ,
26+ '.git' ,
1327 'dist' ,
1428 'build' ,
1529 'out' ,
1630 'coverage' ,
1731 '.turbo' ,
32+ '.vercel' ,
33+ '.netlify' ,
1834]
1935
20- // Specific files to allow (e.g., Next.js pages/index.tsx is legitimate)
21- const ALLOWED_BARREL_FILES = [
22- 'app/page.tsx' , // Next.js 13+ app router root page
23- 'pages/index.tsx' , // Next.js pages router root page
36+ // Index files that are legitimate (not barrel files)
37+ const ALLOWED_INDEX_FILES = [
38+ // Next.js legitimate index files
39+ 'app/page.tsx' , // Next.js 13+ app router root page
40+ 'pages/index.tsx' , // Next.js pages router root page
2441 'pages/index.js' ,
42+ 'pages/index.mjs' ,
2543 'src/pages/index.tsx' ,
2644 'src/pages/index.js' ,
45+ 'src/pages/index.mjs' ,
46+
47+ // Other legitimate index files
48+ 'src/index.ts' , // Package entry point
49+ 'src/index.js' , // Package entry point
50+ 'src/index.mjs' , // ES module entry point
51+ 'src/index.cjs' , // CommonJS entry point
52+ 'lib/index.ts' , // Library entry point
53+ 'lib/index.js' , // Library entry point
54+ 'lib/index.mjs' , // Library ES module entry point
55+ ]
56+
57+ // Patterns that indicate a barrel file (re-exports)
58+ const BARREL_EXPORT_PATTERNS = [
59+ / e x p o r t \s * \* \s * f r o m / g, // export * from './module'
60+ / e x p o r t \s * { \s * .* \s * } \s * f r o m / g, // export { something } from './module'
2761]
2862
63+ // Minimum number of exports to consider it a barrel file
64+ const MIN_EXPORTS_FOR_BARREL = 3
65+
66+ // ============================================================================
67+ // IMPLEMENTATION - No need to modify below this line
68+ // ============================================================================
69+
70+ const fs = require ( 'fs' )
71+ const path = require ( 'path' )
72+
73+ // Check if a file is actually a barrel file by analyzing its content
74+ function isBarrelFile ( filePath ) {
75+ try {
76+ const content = fs . readFileSync ( filePath , 'utf8' )
77+ let exportCount = 0
78+
79+ // Count barrel export patterns
80+ BARREL_EXPORT_PATTERNS . forEach ( pattern => {
81+ const matches = content . match ( pattern )
82+ if ( matches ) {
83+ exportCount += matches . length
84+ }
85+ } )
86+
87+ // Consider it a barrel file if it has enough re-exports
88+ return exportCount >= MIN_EXPORTS_FOR_BARREL
89+ } catch ( error ) {
90+ // If we can't read the file, assume it's not a barrel file
91+ return false
92+ }
93+ }
94+
2995function findBarrelFiles ( dir , basePath = '' ) {
3096 const barrelFiles = [ ]
3197
@@ -41,16 +107,19 @@ function findBarrelFiles(dir, basePath = '') {
41107
42108 if ( entry . isDirectory ( ) ) {
43109 // Skip excluded directories
44- if ( EXCLUDED_DIRS . includes ( entry . name ) ) {
110+ if ( EXCLUDED_DIRECTORIES . includes ( entry . name ) ) {
45111 continue
46112 }
47113
48114 // Recursively check subdirectories
49115 barrelFiles . push ( ...findBarrelFiles ( fullPath , relativePath ) )
50116 } else if ( entry . isFile ( ) && BARREL_FILE_PATTERNS . includes ( entry . name ) ) {
51117 // Check if this barrel file is in the allowed list
52- if ( ! ALLOWED_BARREL_FILES . includes ( relativePath ) ) {
53- barrelFiles . push ( relativePath )
118+ if ( ! ALLOWED_INDEX_FILES . includes ( relativePath ) ) {
119+ // Check if file contains barrel export patterns
120+ if ( isBarrelFile ( fullPath ) ) {
121+ barrelFiles . push ( relativePath )
122+ }
54123 }
55124 }
56125 }
@@ -61,18 +130,34 @@ function findBarrelFiles(dir, basePath = '') {
61130function main ( ) {
62131 console . log ( '🔍 Checking for barrel files...' )
63132
133+ // Parse command line arguments
134+ const args = process . argv . slice ( 2 )
135+ const specificDirs = args . filter ( arg => ! arg . startsWith ( '--' ) )
136+
137+ // Determine directories to search
138+ const searchDirs = specificDirs . length > 0 ? specificDirs : [ '.' ]
139+
140+ // Find barrel files in all specified directories
141+ let allBarrelFiles = [ ]
142+ searchDirs . forEach ( dir => {
143+ if ( fs . existsSync ( dir ) ) {
144+ const barrelFiles = findBarrelFiles ( dir )
145+ allBarrelFiles = allBarrelFiles . concat ( barrelFiles )
146+ }
147+ } )
148+
64149 // Exclude generated types directory from barrel checks
65- const barrelFiles = findBarrelFiles ( '.' ) . filter ( ( p ) => ! p . startsWith ( 'lib/types/generated' ) )
150+ allBarrelFiles = allBarrelFiles . filter ( ( p ) => ! p . startsWith ( 'lib/types/generated' ) )
66151
67- if ( barrelFiles . length > 0 ) {
152+ if ( allBarrelFiles . length > 0 ) {
68153 console . log ( '❌ COMMIT BLOCKED: Barrel files are not allowed!' )
69154 console . log ( '' )
70155 console . log ( '💡 Use explicit imports instead of barrel files:' )
71156 console . log ( " ❌ import { Component } from '@/components'" )
72157 console . log ( " ✅ import { Component } from '@/components/component'" )
73158 console . log ( '' )
74159 console . log ( '📋 Found barrel files:' )
75- barrelFiles . forEach ( ( file ) => {
160+ allBarrelFiles . forEach ( ( file ) => {
76161 console . log ( ` - ${ file } ` )
77162 } )
78163 console . log ( '' )
@@ -83,7 +168,7 @@ function main() {
83168 console . log ( ' • Faster builds and IDE performance' )
84169 console . log ( ' • More explicit and maintainable code' )
85170 console . log ( '' )
86- console . log ( '🛠️ To fix: Remove index.ts files and update imports to be explicit' )
171+ console . log ( '🛠️ To fix: Remove index files and update imports to be explicit' )
87172
88173 if ( process . env . NODE_ENV !== 'development' ) {
89174 process . exit ( 1 )
@@ -95,4 +180,21 @@ function main() {
95180 process . exit ( 0 )
96181}
97182
98- main ( )
183+ // Run validation (only if called directly)
184+ if ( require . main === module ) {
185+ main ( )
186+ }
187+
188+ // Export for use in ESLint plugin
189+ module . exports = {
190+ BARREL_FILE_PATTERNS ,
191+ EXCLUDED_DIRECTORIES ,
192+ ALLOWED_INDEX_FILES ,
193+ BARREL_EXPORT_PATTERNS ,
194+ MIN_EXPORTS_FOR_BARREL ,
195+ isBarrelFile,
196+ findAllBarrelFiles : ( ) => {
197+ const barrelFiles = findAllIndexFiles ( )
198+ return barrelFiles . filter ( file => isBarrelFile ( file ) )
199+ }
200+ }
0 commit comments