Search code examples
javascriptwebpackimportcircular-dependencydirectory-structure

Webpack circular dependency while importing from index files


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:

  • something from files/ imports something from api/index.ts
  • something from api/ imports something from files/index.ts

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 anything
  • files/utils > downloadFile depends on api/api > api.get, but api.get does not depend on anything

As 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:

  • How to keep the ability to import from index files and be safe of circular dependency problem? (not local index files, as you can see in api.ts, I am not importing utils from . (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/index
  • If the above is not possible, do I have to separate each function in individual file? Does that mean using index files some day might cause circular dependency and we should avoid using them? Should we always import from the file itself, instead of the index file?

Solution

  • This 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 layer n+1 but not the other way round. So, if we consider api and files to be two modules, then think of which one is the higher order module that calls into lower-order module e.g. api -> files.