Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling of circular dependencies #1089

Closed
ondras opened this issue Oct 31, 2016 · 40 comments
Closed

Handling of circular dependencies #1089

ondras opened this issue Oct 31, 2016 · 40 comments

Comments

@ondras
Copy link

ondras commented Oct 31, 2016

Can be seen here.

The problem is that the Child class cannot really inherit from the Parent, because they come out serialized in a wrong order. This is because a circular dependency on doStuff from ./lib.js exists in Parent, while doStuff depends on Child.

In this case, however, I believe that a proper solution exists: serialize lib.js first, then parent.js, then child.js (the parent-child order is necessary due to inheritance; the lib-child order can be switched as necessary, as doStuff needs the child to be present later (when executing).

@wearhere
Copy link

wearhere commented Nov 2, 2016

I can also reproduce this. If Rollup can't automatically fix this it would be nice if it could at least detect the potential for problem and output a warning. It looks like it should do this here but I'm not seeing such a warning emitted.

@ondras
Copy link
Author

ondras commented Nov 2, 2016

@wearhere Thanks for the info about a warning (that is implemented but not printed). This would be obviously a large improvement for my case.

@wearhere
Copy link

wearhere commented Nov 2, 2016

A relatively simple (?) workaround would be to allow users to direct Rollup to load modules in a particular partial order, like "Rollup load these modules first regardless of their imports", the idea being that the user might know those imports to be lazy even if Rollup couldn't figure that out. I looked at the plugin API and didn't immediately see if/how a plugin might control this.

@Rich-Harris
Copy link
Contributor

Yes, we need to reinstate that warning (it was a casualty of a recent refactoring). Rollup adheres to the specification when it comes to module ordering – manually specifying the order isn't really a great solution (as tempting as it sounds) because you'll probably have broken behaviour if you subsequently move away from Rollup.

Basically, some module (maybe your entry point) needs to import one of the modules before one of the other modules. It's often not particularly obvious which import needs to go where, sadly (see styled-components/styled-components#100 for a similar tale of woe).

@wearhere
Copy link

Rollup adheres to the specification when it comes to module ordering… Basically, some module (maybe your entry point) needs to import one of the modules before one of the other modules.

@Rich-Harris are you saying that the specification requires the broken order—that Rollup can't choose the solution @ondras outlines at top without violating the spec?

@Rich-Harris
Copy link
Contributor

@wearhere yep

@cowwoc
Copy link

cowwoc commented Feb 13, 2017

@Rich-Harris

Basically, some module (maybe your entry point) needs to import one of the modules before one of the other modules.

Could you please clarify how this example could be reworked? No amount of reordering the imports seems to do the trick for me.

The desired output is to define Parent before Child but I can't figure out how to get rollup to produce such output.

@cowwoc
Copy link

cowwoc commented Feb 23, 2017

To answer my last question, the only way I found to handle circular dependencies is to declare modules without any circular references, then attach functions that contain circular dependencies after-the-fact.

In other words, this becomes that.

This means that each class needs two modules. A private one that does not contain circular dependencies (only imports other private modules), and a public one that loads circular dependencies in the correct order. Users only import the public modules.

I hope this helps others.

@incompl
Copy link

incompl commented Jan 9, 2018

@cowwoc It looks like your links for "this" and "that" no longer work, and I'd be really curious to see. I'm dealing with this issue in a project right now.

@cowwoc
Copy link

cowwoc commented Jan 10, 2018

@incompl I don't have any examples I can point to off-hand, but going by my old design notes:

// DESIGN:
// * internal/ObjectVerifier declares ObjectVerifier without any circular dependencies.
// * First we load internal/ObjectVerifier.
// * Next we load the circular dependencies (classes that depend on ObjectVerifier and vice-versa).
// * Finally, we add methods to ObjectVerifier that reference the circular dependencies.

Here is a concrete example. Say MapVerifier extends ObjectVerifier but we want ObjectVerifier.asMap() to return MapVerifier. So what we do is:

  • Define class ObjectVerifier inside internal/ObjectVerifier.js that contains all the methods ObjectVerifier should have minus the ones that reference circular dependencies.
  • Define each circular dependency to extend internal/ObjectVerifier (no circular dependency so far).
  • Finally, we have ObjectVerifier.js (not to be confused with internal/ObjectVerifier). This file imports internal/ObjectVerifier.js, then the dependencies (e.g. MapVerifier.js) then it adds methods to the previously-defined class ObjectVerifier that would involve circular dependencies. So for example, this file would take the existing ObjectVerifier class and define ObjectVerifier.asMap on it.
  • Now, when end-users import ObjectVerifier.js they get a class containing circular dependencies.

Makes sense?

@incompl
Copy link

incompl commented Jan 10, 2018

Yeah I see what you're saying. In my case, I had a group of classes that relied on each other but they were all part of one conceptual "module" so I made a new file that imports and exposes all of them. In that new file I put the imports in the right order and made sure no code accesses the classes except through the new interface. This has worked out just fine and avoids having to make a new file for each class.

@guybedford
Copy link
Contributor

Circular reference warnings in Rollup have now been reinstated. Hopefully this goes some way to helping identify these issues now.

@btakita
Copy link
Contributor

btakita commented Feb 12, 2018

Is/Can there be a way to disable circular dependency warnings?

@guybedford
Copy link
Contributor

@btakita if we get feedback that these warnings are too noisy, we will definitely consider options here.

@btakita
Copy link
Contributor

btakita commented Feb 12, 2018 via email

@donmccurdy
Copy link

For example I'm getting a screen full of these warnings when including D3 in my project:

Details
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/select.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/selectAll.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/filter.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/data.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/data.js -> node_modules/d3-selection/src/selection/enter.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/exit.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/merge.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-selection/src/selection/index.js -> node_modules/d3-selection/src/selection/sort.js -> node_modules/d3-selection/src/selection/index.js
(!) Circular dependency: node_modules/d3-interpolate/src/value.js -> node_modules/d3-interpolate/src/array.js -> node_modules/d3-interpolate/src/value.js
(!) Circular dependency: node_modules/d3-interpolate/src/value.js -> node_modules/d3-interpolate/src/object.js -> node_modules/d3-interpolate/src/value.js
(!) Circular dependency: node_modules/d3-transition/src/transition/index.js -> node_modules/d3-transition/src/transition/filter.js -> node_modules/d3-transition/src/transition/index.js
(!) Circular dependency: node_modules/d3-transition/src/transition/index.js -> node_modules/d3-transition/src/transition/merge.js -> node_modules/d3-transition/src/transition/index.js
(!) Circular dependency: node_modules/d3-transition/src/transition/index.js -> node_modules/d3-transition/src/transition/select.js -> node_modules/d3-transition/src/transition/index.js
(!) Circular dependency: node_modules/d3-transition/src/transition/index.js -> node_modules/d3-transition/src/transition/selectAll.js -> node_modules/d3-transition/src/transition/index.js
(!) Circular dependency: node_modules/d3-transition/src/transition/index.js -> node_modules/d3-transition/src/transition/transition.js -> node_modules/d3-transition/src/transition/index.js
(!) Circular dependency: node_modules/d3-voronoi/src/Diagram.js -> node_modules/d3-voronoi/src/Beach.js -> node_modules/d3-voronoi/src/Cell.js -> node_modules/d3-voronoi/src/Edge.js -> node_modules/d3-voronoi/src/Diagram.js
(!) Circular dependency: node_modules/d3-voronoi/src/Diagram.js -> node_modules/d3-voronoi/src/Beach.js -> node_modules/d3-voronoi/src/Cell.js -> node_modules/d3-voronoi/src/Diagram.js
(!) Circular dependency: node_modules/d3-voronoi/src/Diagram.js -> node_modules/d3-voronoi/src/Beach.js -> node_modules/d3-voronoi/src/Circle.js -> node_modules/d3-voronoi/src/Diagram.js
(!) Circular dependency: node_modules/d3-voronoi/src/Diagram.js -> node_modules/d3-voronoi/src/Beach.js -> node_modules/d3-voronoi/src/Diagram.js
created dist/aila-map.min.js in 2.7s

@guybedford
Copy link
Contributor

//cc @Rich-Harris @lukastaegert @kellyselden see above for an example of the reinstated circular reference output. This almost gets into warning flags territory, which I guess is a standard feature for a compiler.

@lukastaegert
Copy link
Member

I guess at the moment, you could supply a custom "onwarn" function to only display certain warnings. Would be nice to also be able to supply e.g. an array of warning codes to silence. To be able to do that, however, a code would need to be displayed as part of each warning so that the user knows how to silence a certain warning.

@shannonmoeller
Copy link

Using this as a workaround. Would love a flags feature.

function onwarn(warning) {
    if (warning.code !== 'CIRCULAR_DEPENDENCY') {
        console.error(`(!) ${warning.message}`);
    }
}

@guybedford
Copy link
Contributor

//cc @adrianheine

@rpadovani
Copy link

I'd like in general to avoid warning about circular dependencies in node_modules/ - there isn't much I can do about them

@adrianheine
Copy link
Contributor

Quite often, circular dependencies are the cause for some issue that's opened here or in rollup-plugin-commonjs. I'm hoping that warning about them will lead people into the right direction (that is, working around the cycle or opening an issue at the package containing it). I'm totally fine with being able to turn them off easily, but I think they should be enabled by default.

@guybedford
Copy link
Contributor

Created #1978.

@piotr-cz
Copy link

piotr-cz commented Jul 3, 2018

To silence circular dependencies warnings for let's say moment library use:

// rollup.config.js

import path from 'path'

const onwarn = warning => {
  // Silence circular dependency warning for moment package
  if (
    warning.code === 'CIRCULAR_DEPENDENCY'
    && !warning.importer.indexOf(path.normalize('node_modules/moment/src/lib/'))
  ) {
    return
  }

  console.warn(`(!) ${warning.message}`)
}

export default {
  input: 'foobar.js',
  onwarn,
  // ...
}

@mycarrysun
Copy link

@incompl thanks for your suggestion - just got done removing about 10 circular dependencies.

Is there any way we can add what element is being referenced circularly? This would help with debugging a TON,

@lukastaegert
Copy link
Member

@mycarrysun What do you have in mind here i.e. could you sketch what output would help you here? Note that for a circular dependency warning involving n modules, n different imports are involved.

@lukastaegert
Copy link
Member

More precisely, at least n different import statements each of which could possibly import several items.

@mycarrysun
Copy link

mycarrysun commented Jul 13, 2018

@lukastaegert if you could grab the imported item name? Is that possible?
For example:

// utils.js
import { foo, bar } from '../../myModule'
import { baz } from '../../myOtherModule'

export function myOriginalFunction(){
  foo(() => bar())
}

// circular.js
import { myCoolFunction } from './utils'

export function myCircularFunction(){
  myOriginalFunction()
}

// index.js - this is the entry file
export * from './utils'
export * from './circular'

Currently this would print something like:

Circular dependency: index.js -> circular.js -> utils.js -> index.js

Something like this would help to know which modules are being imported circularly:

Circular dependency: index.js -> myCircularFunction -> myOriginalFunction -> index.js

I'm not sure how to handle the export * syntax as the module name may not be available? I don't know enough about the inner workings of modules to know how all of this is being handled behind the scenes.

@lukastaegert
Copy link
Member

I see. So the exported entities listed are not necessarily exhaustive but give you examples, which culprits you should handle differently. There are still some edge cases, e.g. how to handle something like import './foo.js' where no exports are imported but just the module is "run"; or how to handle renamed imports i.e. import {foo as bar} from './foo.js'. But I see how this could help with debugging.

Maybe a combined output would be helpful, something like

Circular dependency: index.js -> circular.js (myCircularFunction) -> utils.js (myOriginalFunction) -> index.js (whateEverElseIsImportedFromIndex)

So we just list the first imported entity (or possibly default if it is a default import). Could be even formatted like this:

Circular dependency: index.js
  -> circular.js (myCircularFunction)
  -> utils.js (myOriginalFunction)
  -> index.js (whateEverElseIsImportedFromIndex)

I assume this would fit your needs?

@mycarrysun
Copy link

@lukastaegert yes that would be great!! Currently I have 4 circular dependencies and it is already starting to look crowded so I could see how the first example may be the preferred format:

Circular dependency: index.js -> circular.js (myCircularFunction) -> utils.js (myOriginalFunction) -> index.js (whateEverElseIsImportedFromIndex)

I like the second example if there were only 1 or 2 messages but with more than 4 or 5 it would start to be too much output.

@lukastaegert
Copy link
Member

Another thing we could do to limit output would be to only every show the first circular dependency warning. I think we already do this for other types of warnings. Then you would need to tackle the warnings one-by-one, though.

@mycarrysun
Copy link

Yea - I think my personal preference would be to see them all at once. Or maybe limit it to up to 10 messages and then list the count of how many more messages were not displayed.

@patrickroberts
Copy link

Especially when rollup is configured with multiple outputs, I find this particular onwarn to be helpful in reducing warning clutter. It just displays each circular reference once and doesn't repeat the warning for each output:

const silence = new Map()

function onwarn (warning, warn) {
  const ignore = silence.get(warning.code)

  if (ignore) {
    if (ignore.has(warning.message)) return

    ignore.add(warning.message)
  } else {
    silence.set(warning.code, new Set().add(warning.message))
  }

  warn(warning)
}

@cowwoc
Copy link

cowwoc commented Jul 26, 2019

For what it's worth, I wanted to chime in in support of this feature. I initially thought that circular dependency warnings were too noisy and contained false-positives. Fast forward a couple of months, I finally got around to investigating further and sure enough the warnings were legitimate. Every single one.

This output has been extremely useful in helping me track down problems in my own code. Thank you very much for this. This feature is very useful!

@davestewart
Copy link

davestewart commented May 22, 2020

Agree with the previous suggestion of formatting:

src/lib/components/index.ts
    -> src/lib/components/app/AppWorkspace.vue
    -> src/lib/components/app/AppWorkspace.vue?rollup-plugin-vue=script.js
    -> src/lib/components/app/AppWindow.vue
    -> src/lib/components/app/AppWindow.vue?rollup-plugin-vue=script.js
    -> src/lib/components/app/AppTab.vue
    -> src/lib/components/app/AppTab.vue?rollup-plugin-vue=script.js
    -> src/lib/components/index.ts

@zachhardesty7
Copy link

zachhardesty7 commented May 28, 2020

Figured I'd drop the consolidated function I'm using based on #1089 (comment) that also keeps the formatting performed by rollup thanks to #2271 (comment). I also included the ability to define more warning codes from a package to ignore.

const onwarn = (warning, rollupWarn) => {
  const ignoredWarnings = [
    {
      ignoredCode: 'CIRCULAR_DEPENDENCY',
      ignoredPath: 'node_modules/semantic-ui-react/dist/',
    },
  ]

  // only show warning when code and path don't match
  // anything in above list of ignored warnings
  if (!ignoredWarnings.some(({ ignoredCode, ignoredPath }) => (
    warning.code === ignoredCode &&
	warning.importer.includes(path.normalize(ignoredPath))))
  ) {
    rollupWarn(warning)
  }
}

@piotr-cz
Copy link

piotr-cz commented Sep 2, 2022

By the way, now the importer path tested may be simlified to

warning.importer.startsWith('node_modules/joi/lib/')

@ShaMan123
Copy link

ShaMan123 commented Mar 6, 2023

+1
I would even make an option to be an error and not just a warning. As a repo maintainer I want to force no circular deps.

@dhowe
Copy link

dhowe commented Oct 25, 2023

Is there a simple/final workaround here to ignore circular dependency warnings for a specific library ?

@ShaMan123
Copy link

ShaMan123 commented Oct 26, 2023

// rollup.config

/**
 * disallow circular deps
 * @see https://rollupjs.org/configuration-options/#onwarn
 * @param {*} warning
 * @param {*} warn
 */
function onwarn(warning, warn) {
  if (
    (warning.code === 'PLUGIN_WARNING' &&
      !warning.message.includes('sourcemap')) ||
    warning.code === 'CIRCULAR_DEPENDENCY'
  ) {
    console.error(chalk.redBright(warning));
    if (process.env.CI) {
      throw Object.assign(new Error(), warning);
    }
  }
  warn(warning);
}

export default [
  {
    ...
    onwarn
  }
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests