Favour inline exports in JavaScript
I have always leaned to towards inlined exports in a module.
export function foo() {
return 'bar';
}
Compared to this example where the export is at the end of the file.
export function foo() {
return 'bar';
}
export { foo };
I think I might have heard/read at some point that someone else favoured this approach also but I cannot remember what the reasons were, anyway it reinforced my preference and I have stuck with it.
Recently I ran into an issue whilst refactoring that has further reinforced this preference.
Original file
The following module shows multiple exported functions listed at the end of the file.
src/utils/auth.ts
function validateToken(token: string) {
// token validation logic
}
function generateToken(userId: string) {
// token generation logic
}
function parseToken(token: string) {
// token parsing logic
};
export {
validateToken,
generateToken,
parseToken
};
Refactor
Later, you decide to move parseToken
to its own file for improved organisation. I actually had to do this to avoid a circular dependency but that is not the main point here.
src/utils/parse-token.ts
export function parseToken(token: string) {
// token parsing logic
}
Here is where the mistake happened. You update the original file, but keep the export list. Typescript did not complain and my IDE did not warn me. Am I missing some config to catch this?
src/utils/parse-token.ts
import { parseToken } from './parse-token'; // Imported the moved function
function validateToken(token: string) {
// token validation logic
}
function generateToken(userId: string) {
// token generation logic
}
export {
validateToken,
generateToken,
parseToken // 😱 Accidentally re-exporting the imported function!
};
Problem
Now other files can still import parseToken from either location:
src/services/user-service.ts
// This still works, but shouldn't!
import { parseToken } from './utils/auth';
// This is the correct import we want to enforce
import { parseToken } from './utils/parse-token';
Solution
Remove export lists entirely and use inline exports. This makes it obvious when you’re accidentally re-exporting something:
src/utils/auth.ts
import { parseToken } from './parse-token'; // Only imported for internal use
export function validateToken(token: string) {
// token validation logic
}
export function generateToken(userId: string) {
// token generation logic
}
// No export list = no accidental re-exports! 😊
Conclusion
My personal preference to always inline the export feels validated, I will sleep better now!
- makes re-exports explicit and intentional
- forces proper import updates during refactoring
- keeps source of truth clear
- prevents circular dependency bugs
Can inlined exports be enforced, should they be enforced? I need to check that out.