I have the current folder structure
├── files/
│ └── utils.ts
│ └── index.ts
├── api/
│ ├── api.ts
│ ├── utils.ts
│ └── index.ts
Content:
// files/utils.ts
import api from 'api' // imported directly from api/index.ts
export const fileToBase64 = (...) => ... // does something
export const downloadFile = (...) => api.get(...) // downloads a file using api
// files/index.ts
export * from './utils'
// api/utils.ts
import { fileToBase64 } from 'files' // imported directly from files/index.ts
export const parseData = (...) => ... fileToBase64(...)... // does something with fileToBase64
// api/api.ts
import { parseData } from './utils'
export const api = {
post: (...) => ... const newData = parseData(...) ..., // uses parseData from './utils'
get...
}
// api/index.ts
export * from './api'
Both eslint
and dpdm
package complain about circular dependency. Reason:
The only reason they complain is because I do imports from index files
Actually there is no circular dependency, because:
api/utils > parseData
depends on files/utils > fileToBase64
, but fileToBase64
does not depend on anythingfiles/utils > downloadFile
depends on api/api > api.get
, but api.get
does not depend on anythingAs a proof, when I move fileToBase64
into separate file files/convertFile.ts
and in api/utils.ts
import it from files/convertFile
(not from files/index), both tools stop complaining about circular dependency
I have 2 questions here:
.
(local index), but from utils.ts
directly from the original file.). I want to import from index files when the files are not in same directory, like the example, things from api/ import from files/index and things from files/ import from api/indexThis pattern of using index
files is also known as a barrel export. While the pattern provides for cleaner imports in many cases, there are quite some challenges with using excessive barrels in terms of performance and tree-shaking (especially more so when you use namespaced exports like export * from './utils'
).
Now, let's come back to bundlers. Bundlers generally work at file/module level. The tree-shaking optimization comes later in the end after the files have been bundled together into a single file. So, from what you have shared, you do have a cycle in your code. It goes like this:
api/utils -> files/index -> files/utils -> api/index -> api/api -> api/utils
Even if you are not actually using all the functions, the bundler does not see that at export level. It always looks for circular dependencies at module-level.
Your approach of moving the common functions into a dedicated file is the correct approach. You have to do exactly that. You can either keep it in multiple files or have one file for such common functions ensuring no circular dependency.
As a side note, think of your system modules in layers. A layer
n
can talk to layern+1
but not the other way round. So, if we considerapi
andfiles
to be two modules, then think of which one is the higher order module that calls into lower-order module e.g. api -> files.