{"id":10190,"date":"2018-06-27T00:38:50","date_gmt":"2018-06-26T21:38:50","guid":{"rendered":"https:\/\/railsware.com\/blog\/?p=10190"},"modified":"2025-01-31T19:39:30","modified_gmt":"2025-01-31T16:39:30","slug":"how-to-analyze-circular-dependencies-in-es6","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/how-to-analyze-circular-dependencies-in-es6\/","title":{"rendered":"How to Analyze Circular Dependencies in ES6?"},"content":{"rendered":"\n<p class=\"intro-text\">Have you ever came across a dreadful and enigmatic error message stating that something like `__WEBPACK_IMPORTED_MODULE_8__node_details__[&#8220;a&#8221; \/* default *\/]` is undefined? I have a few times already\u2026<\/p>\n\n\n\n<p>It is caused by circular dependencies that cannot be resolved synchronously by webpack. One is undefined, hence the error. Let me explain what it means, how it can be tackled and resolved once and for all.<\/p>\n\n\n\n<p>Before we dive into this topic let me explain the context and environment used for the investigation. The problem arose in a JS application built with webpack 3.8 using babel and ES6 imports. It is under heavy refactor from jQuery to be fully based on React and Redux.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"2400\" height=\"1260\" src=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/ES6-v2-illustration.jpg\" alt=\"Circular dependencies in ES6\" class=\"wp-image-10217\" srcset=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/ES6-v2-illustration.jpg 2400w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/ES6-v2-illustration-360x189.jpg 360w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/ES6-v2-illustration-768x403.jpg 768w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/ES6-v2-illustration-1024x538.jpg 1024w\" sizes=\"auto, (max-width: 2400px) 100vw, 2400px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Circular dependencies<\/h2>\n\n\n\n<p>If you are not familiar with the term, let me briefly explain what that is and why would you prefer to avoid that in your code.<\/p>\n\n\n\n<p>According to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Circular_dependency\" target=\"_blank\" rel=\"nofollow noopener\">Wikipedia<\/a>, <em>In software engineering, a circular dependency is a relation between two or more modules which either directly or indirectly depend on each other to function properly.<\/em><\/p>\n\n\n\n<p>They are not always evil, but you might want to treat them with special care. They cause tight coupling of the mutually dependent modules. These kinds of modules are harder to understand and reuse as doing so might cause a ripple effect where a local change to one module has global effects. It might indicate lack of a larger context or proper architecture. A good architecture imposes uni-directional flow between modules and entire layers. Top-level modules depend on lower-level modules whose entry points depend on their own internals in turn. In larger systems, though, problems sometimes come up, especially during refactoring.<\/p>\n\n\n\n<p>On the other hand, circular dependencies are sometimes needed, i.e. in tree structures where parents refer to children and vice-versa. In functional programming it\u2019s even encouraged to have recursive definitions to some extent. Nonetheless I would strongly discourage you from intentionally introducing circular dependencies in JavaScript.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">ES6 Circular dependencies<\/h3>\n\n\n\n<p>In ES6 modules can have a single default export as well as many more granular named ones. Each module can be thought of as a single file. One of the key design goals of ES6 modules was support for circular dependencies. <a href=\"https:\/\/exploringjs.com\/es6\/ch_modules.html#sec_design-goals-es6-modules\" target=\"_blank\" rel=\"nofollow noopener\">There were a few reasons for that<\/a>. The main reason is not to break the module system while refactoring and let software engineers decide if it\u2019s needed. Webpack supports that nicely. It\u2019s worth noting that using <code>const<\/code> over <code>function<\/code> while defining functions prevents function hoisting within a single module and ensures the absence of circular dependencies within a single module.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What causes the problem?<\/h3>\n\n\n\n<p>The synchronous cycle of operations on imported values and\/or function calls appear to be problematic. There are two symptoms I\u2019ve run into:<\/p>\n\n\n\n<p>Imported value is undefined when it belongs to a cycle &#8211; it happens for both expressions and function definitions.<br>It might appear that the import is undefined, but can also cause unexpected behavior due to the way <code>undefined<\/code> is handled in JS, i.e. there might be <code>NaN<\/code> instead of a number.<br>In our case it happened in unit tests first as the dependency tree is smaller and entry point is different than in the browser.<\/p>\n\n\n\n<p>Bear in mind that cycles can be of any size. The smallest cycle consists of two modules. Here are some simplified examples to make it easier to understand:.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"360\" height=\"158\" src=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-1-360x158.jpg\" alt=\"synchronous cycle between A and B\" class=\"wp-image-10218\" srcset=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-1-360x158.jpg 360w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-1-768x337.jpg 768w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-1-1024x450.jpg 1024w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-1.jpg 2000w\" sizes=\"auto, (max-width: 360px) 100vw, 360px\" \/><\/figure><\/div>\n\n\n<p><br><em style=\"display: block; text-align: center; font-size: 80%;\">Pic. 1. Synchronous cycle between A and B<\/em><\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default 3 + B;\n\n\/\/ B.js\nimport A from '.\/A';\n\nexport default 4 + A;\n\n\/\/ index.js\nimport A from '.\/A';\n\nconsole.log(A); \/\/ NaN (adding number to undefined)\n<\/pre>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default class A extends B {};\n\n\/\/ B.js\nimport A from '.\/A';\n\nexport default class B extends A {};\n\n\/\/ index.js\nimport A from '.\/A'; \/\/ TypeError: Super expression must either be null or a function, not undefined\n<\/pre>\n\n\n\n<p>RangeError: exceeding call stack &#8211; it happens when imports form a cycle of immediate synchronous function calls.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default () =&gt; 3 + B();\n\n\/\/ B.js\nimport A from '.\/A';\n\nexport default () =&gt; 4 + A();\n\n\/\/ index.js\nimport A from '.\/A';\n\nA(); \/\/ RangeError: Maximum call stack size exceeded\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">What doesn\u2019t cause the problem?<\/h3>\n\n\n\n<p>Function calls don\u2019t cause the problem when cycle is asynchronous meaning that directly referenced functions are not called immediately.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter\"><img loading=\"lazy\" decoding=\"async\" width=\"360\" height=\"158\" src=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-2-360x158.jpg\" alt=\"partially asynchronous cycle between A and B\" class=\"wp-image-10219\" srcset=\"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-2-360x158.jpg 360w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-2-768x338.jpg 768w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-2-1024x450.jpg 1024w, https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/scheme-2.jpg 2000w\" sizes=\"auto, (max-width: 360px) 100vw, 360px\" \/><\/figure><\/div>\n\n\n<p><br><em style=\"display: block; text-align: center; font-size: 80%;\">Pic. 2. Partially asynchronous cycle between A and B<\/em><\/p>\n\n\n\n<p>Two cases would be:<\/p>\n\n\n\n<p>Cycle of function calls when one continues chain through a DOM event listener being async, i.e. waiting for user click.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default () =&gt; {\n  console.log('A called');\n  document.addEventListener('click', B, false);\n};\n\n\/\/ B.js\nimport A from '.\/A';\n\nexport default () =&gt; {\n  console.log('B called');\n  A();\n};\n\n\/\/ index.js\nimport A from '.\/A';\n\nA(); \/\/ right away : A called, after click : B called, A called\n<\/pre>\n\n\n\n<p>Referring one class from another as long as the dependency is not necessary for immediate evaluation of class prototype &#8211; otherwise it fails as shown previously.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default class A {\n  static getB() {\n    return new B();\n  }\n};\n\n\/\/ B.js\nimport A from '.\/A';\n\nexport default class B {\n  constructor() {\n    this.a = new A();\n  }\n};\n\n\/\/ index.js\nimport A from '.\/A';\n\nconsole.log(A.getB().a); \/\/ instance of A\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Dependency analysis<\/h2>\n\n\n\n<p>Let\u2019s find out where the circular dependencies are. For the analysis entire modules (files) are treated as indivisible units. Dependencies in a project can be represented as a directed graph where the beginning of an edge is the importing module (file where <code>import<\/code> is used) and an end of it is the exporting module (file with <code>export<\/code>). According to <a href=\"https:\/\/mathworld.wolfram.com\/CyclicGraph.html\" target=\"_blank\" rel=\"nofollow noopener\">WolframAlpha<\/a>, <em>A cyclic graph is a graph containing at least one graph cycle. A graph that is not cyclic is said to be acyclic (&#8230;) Cyclic graphs are not trees.<\/em> Dependency graph should be acyclic. Hence the term <em>dependency tree<\/em>. Our goal is to identify cycles in the graph.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Extracting information about imports from code<\/h3>\n\n\n\n<p>ES6 Module statements can be read and statically analyzed with <a href=\"https:\/\/www.npmjs.com\/package\/analyze-es6-modules\" target=\"_blank\" rel=\"nofollow noopener\">analyze-es6-modules package<\/a>.<\/p>\n\n\n\n<p>Let\u2019s start by adding the package to the project as a development dependency.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:bash\">yarn add analyze-es6-modules -D\n<\/pre>\n\n\n\n<p>The next step includes necessary configuration which in our case is as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const analyzeModules = require('analyze-es6-modules');\n\nconst configuration = {\n  cwd: 'app\/assets\/javascripts',\n  sources: ['**\/*.js'],\n  babel: {\n    plugins: [\n      require('babel-plugin-syntax-jsx'),\n      require('babel-plugin-syntax-flow'),\n      require('babel-plugin-syntax-object-rest-spread'),\n    ],\n  },\n};\n\nconst resolvedHandler = ({ modules }) =&gt; {\n  \/\/ do something with extracted modules\n};\n\nconst rejectedHandler = () =&gt; {\n  console.log('rejected!');\n};\n\nanalyzeModules(configuration).then(resolvedHandler, rejectedHandler);\n<\/pre>\n\n\n\n<p>You might need to adjust the config as:<\/p>\n\n\n\n<p><code>cwd<\/code> is path to a directory holding all of JS sources (without <code>node_modules<\/code>)<br><code>sources<\/code> is a list of globbing patterns pointing to source files<br><code>babel.plugins<\/code> is a list of babel plugins necessary to parse the files as non-standard syntax might be used. Safe option is to list all the packages that start with <code>babel-plugin-syntax<\/code>. The list can be obtained from <code>yarn.lock<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:bash\">egrep '^babel-plugin-syntax' yarn.lock\n<\/pre>\n\n\n\n<p>The next step is to build an intermediary representation in which each import statement gets represented as an ordered pair. The set is used as it\u2019s a container holding only unique items.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const collectDependencies = (modules) =&gt; {\n  const dependencySet = new Set();\n  const separator = ',';\n\n  modules.forEach(({ path, imports }) =&gt; {\n    const importingPath = path;\n\n    imports.forEach(({ exportingModule }) =&gt; {\n      const exportingPath = exportingModule.resolved;\n      const dependency = [importingPath, exportingPath].join(separator);\n\n      dependencySet.add(dependency);\n    });\n  });\n\n  return Array.from(dependencySet.values()).map(it =&gt; it.split(separator));\n};\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Building directed graph<\/h3>\n\n\n\n<p>Having collected all the unique ordered pairs, they can be thought of as edges of a directed graph. Hence all that is needed is to group edges by source node and the graph represented as a hash map will be ready.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const buildDirectedGraphFromEdges = (edges) =&gt; {\n  return edges.reduce((graph, [sourceNode, targetNode]) =&gt; {\n    graph[sourceNode] = graph[sourceNode] || new Set();\n    graph[sourceNode].add(targetNode);\n\n    return graph;\n  }, {});\n};\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Minimizing graph to contain only cycles<\/h3>\n\n\n\n<p>At this point graph represents potentially all the module statements in the project. Most of them are not needed as they are not participating to any cycle. Hence can be removed from the graph as they are irrelevant to our analysis. Such edges share common characteristic \u2014 they are terminal on at least one end. When a module is not imported anywhere, it is a terminal node at the beginning of an edge. A terminal end node is a module that does not import anything.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const without = (firstSet, secondSet) =&gt; (\n  new Set(Array.from(firstSet).filter(it =&gt; !secondSet.has(it)))\n);\n\nconst mergeSets = (sets) =&gt; {\n  const sumSet = new Set();\n  sets.forEach((set) =&gt; {\n    Array.from(set.values()).forEach((value) =&gt; {\n      sumSet.add(value);\n    });\n  });\n  return sumSet;\n};\n\nconst stripTerminalNodes = (graph) =&gt; {\n  const allSources = new Set(Object.keys(graph));\n  const allTargets = mergeSets(Object.values(graph));\n\n  const terminalSources = without(allSources, allTargets);\n  const terminalTargets = without(allTargets, allSources);\n\n  const newGraph = Object.entries(graph).reduce((smallerGraph, [source, targets]) =&gt; {\n    if (!terminalSources.has(source)) {\n      const nonTerminalTargets = without(targets, terminalTargets);\n\n      if (nonTerminalTargets.size &gt; 0) {\n        smallerGraph[source] = nonTerminalTargets;\n      }\n    }\n\n    return smallerGraph;\n  }, {});\n\n  return newGraph;\n};\n<\/pre>\n\n\n\n<p>The process of stripping terminal nodes can be repeated as long as the graph gets smaller.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const calculateGraphSize = (graph) =&gt; mergeSets(Object.values(graph)).size;\n\nconst miminizeGraph = (graph) =&gt; {\n  const smallerGraph = stripTerminalNodes(graph);\n\n  if (calculateGraphSize(smallerGraph) &lt; calculateGraphSize(graph)) {\n    return miminizeGraph(smallerGraph);\n  } else {\n    return smallerGraph;\n  }\n};\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Graph visualization with DOT<\/h3>\n\n\n\n<p>At this point, the minimal graph has been identified, but it would be helpful to visualize it as it might be difficult to understand the cycles from a plain object representation. Simple way to do so is to represent the graph using <a href=\"https:\/\/graphviz.gitlab.io\/_pages\/doc\/info\/lang.html\" target=\"_blank\" rel=\"nofollow noopener\">dot language<\/a> and render with <a href=\"https:\/\/graphviz.gitlab.io\/\" target=\"_blank\" rel=\"nofollow noopener\">graphviz<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const convertDirectedGraphToDot = (graph) =&gt; {\n  const stringBuilder = [];\n\n  stringBuilder.push('digraph G {');\n\n  Object.entries(graph).forEach(([source, targetSet]) =&gt; {\n    const targets = Array.from(targetSet);\n    stringBuilder.push('\"' + source + '\" -&gt; { \"' + targets.join('\" \"') + '\" }');\n  });\n\n  stringBuilder.push('}');\n\n  return stringBuilder.join(\"\\n\");\n};\n<\/pre>\n\n\n\n<p>Finally, the <code>resolvedHandler<\/code> is completed and looks as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const resolvedHandler = ({ modules }) =&gt; {\n  const dependencies = collectDependencies(modules);\n  const graph = buildDirectedGraphFromEdges(dependencies);\n  const minimalGraph = miminizeGraph(graph);\n\n  console.log(convertDirectedGraphToDot(minimalGraph));\n};\n<\/pre>\n\n\n\n<p>Printed text can be copied to <a href=\"https:\/\/www.webgraphviz.com\/\" target=\"_blank\" rel=\"nofollow noopener\">an online renderer<\/a>. In our case the graph we got looked as below.<\/p>\n\n\n\n<\/p><svg width=\"100%\" viewBox=\"0.00 0.00 1251.00 692.00\" xmlns=\"https:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"https:\/\/www.w3.org\/1999\/xlink\"><g id=\"graph1\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 688)\"><title>G<\/title><polygon fill=\"white\" stroke=\"white\" points=\"-4,5 -4,-688 1248,-688 1248,5 -4,5\"><\/polygon><g id=\"node1\" class=\"node\"><title>components\/NodeAllocationHeads<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"653\" cy=\"-666\" rx=\"146.294\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"653\" y=\"-661.8\" font-family=\"Times,serif\" font-size=\"14.00\">components\/NodeAllocationHeads<\/text><\/g><g id=\"node4\" class=\"node\"><title>node_allocations<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"705\" cy=\"-594\" rx=\"76.2095\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"705\" y=\"-589.8\" font-family=\"Times,serif\" font-size=\"14.00\">node_allocations<\/text><\/g><g id=\"edge3\" class=\"edge\"><title>components\/NodeAllocationHeads-&gt;node_allocations<\/title><path fill=\"none\" stroke=\"black\" d=\"M665.588,-648.055C671.847,-639.629 679.535,-629.28 686.454,-619.966\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"689.375,-621.904 692.528,-611.789 683.755,-617.729 689.375,-621.904\"><\/polygon><\/g><g id=\"node9\" class=\"node\"><title>node_details<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"601\" cy=\"-522\" rx=\"59.2699\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"601\" y=\"-517.8\" font-family=\"Times,serif\" font-size=\"14.00\">node_details<\/text><\/g><g id=\"edge37\" class=\"edge\"><title>node_allocations-&gt;node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M680.879,-576.765C666.633,-567.176 648.424,-554.92 632.991,-544.532\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"634.525,-541.346 624.275,-538.666 630.616,-547.153 634.525,-541.346\"><\/polygon><\/g><g id=\"node30\" class=\"node\"><title>containers\/NodeAllocations<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"1124\" cy=\"-234\" rx=\"118.259\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"1124\" y=\"-229.8\" font-family=\"Times,serif\" font-size=\"14.00\">containers\/NodeAllocations<\/text><\/g><g id=\"edge38\" class=\"edge\"><title>node_allocations-&gt;containers\/NodeAllocations<\/title><path fill=\"none\" stroke=\"black\" d=\"M771.707,-585.179C893.167,-569.067 1138,-527.146 1138,-451 1138,-451 1138,-451 1138,-377 1138,-336.802 1132.38,-290.511 1128.26,-262.002\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"1131.7,-261.371 1126.77,-251.996 1124.78,-262.402 1131.7,-261.371\"><\/polygon><\/g><g id=\"node5\" class=\"node\"><title>components\/NodeDetailsHeader<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"608\" cy=\"-306\" rx=\"136.293\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"608\" y=\"-301.8\" font-family=\"Times,serif\" font-size=\"14.00\">components\/NodeDetailsHeader<\/text><\/g><g id=\"node8\" class=\"node\"><title>node_details_config<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"306\" cy=\"-234\" rx=\"89.0176\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"306\" y=\"-229.8\" font-family=\"Times,serif\" font-size=\"14.00\">node_details_config<\/text><\/g><g id=\"edge6\" class=\"edge\"><title>components\/NodeDetailsHeader-&gt;node_details_config<\/title><path fill=\"none\" stroke=\"black\" d=\"M543.584,-290.069C493.076,-278.362 423.048,-262.13 372.111,-250.324\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"372.721,-246.872 362.189,-248.024 371.141,-253.692 372.721,-246.872\"><\/polygon><\/g><g id=\"edge7\" class=\"edge\"><title>components\/NodeDetailsHeader-&gt;node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M611.322,-324.232C613.639,-361.324 611.039,-448.981 606.578,-493.712\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"603.078,-493.521 605.439,-503.849 610.034,-494.303 603.078,-493.521\"><\/polygon><\/g><g id=\"node10\" class=\"node\"><title>state\/selectors\/node_details<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"789\" cy=\"-90\" rx=\"116.537\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"789\" y=\"-85.8\" font-family=\"Times,serif\" font-size=\"14.00\">state\/selectors\/node_details<\/text><\/g><g id=\"edge8\" class=\"edge\"><title>components\/NodeDetailsHeader-&gt;state\/selectors\/node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M622.506,-287.849C654.424,-250.112 730.847,-159.756 768.004,-115.824\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"770.891,-117.831 774.676,-107.935 765.546,-113.31 770.891,-117.831\"><\/polygon><\/g><g id=\"node33\" class=\"node\"><title>node_filter<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"854\" cy=\"-162\" rx=\"53.1072\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"854\" y=\"-157.8\" font-family=\"Times,serif\" font-size=\"14.00\">node_filter<\/text><\/g><g id=\"edge41\" class=\"edge\"><title>node_details_config-&gt;node_filter<\/title><path fill=\"none\" stroke=\"black\" d=\"M379.053,-223.668C488.785,-209.652 694.02,-183.435 794.919,-170.547\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"795.462,-174.006 804.938,-169.267 794.575,-167.062 795.462,-174.006\"><\/polygon><\/g><g id=\"node41\" class=\"node\"><title>processes<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"161\" cy=\"-162\" rx=\"48.0528\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"161\" y=\"-157.8\" font-family=\"Times,serif\" font-size=\"14.00\">processes<\/text><\/g><g id=\"edge42\" class=\"edge\"><title>node_details_config-&gt;processes<\/title><path fill=\"none\" stroke=\"black\" d=\"M273.097,-217.116C250.919,-206.409 221.609,-192.259 198.522,-181.114\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"199.896,-177.891 189.369,-176.695 196.853,-184.195 199.896,-177.891\"><\/polygon><\/g><g id=\"node42\" class=\"node\"><title>process_allocations<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"161\" cy=\"-90\" rx=\"86.4854\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"161\" y=\"-85.8\" font-family=\"Times,serif\" font-size=\"14.00\">process_allocations<\/text><\/g><g id=\"edge43\" class=\"edge\"><title>node_details_config-&gt;process_allocations<\/title><path fill=\"none\" stroke=\"black\" d=\"M289.664,-216.059C272.15,-197.997 243.426,-168.657 218,-144 207.973,-134.276 196.75,-123.764 186.932,-114.685\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"189.135,-111.956 179.409,-107.754 184.392,-117.104 189.135,-111.956\"><\/polygon><\/g><g id=\"node43\" class=\"node\"><title>resources<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"356\" cy=\"-162\" rx=\"47.2416\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"356\" y=\"-157.8\" font-family=\"Times,serif\" font-size=\"14.00\">resources<\/text><\/g><g id=\"edge44\" class=\"edge\"><title>node_details_config-&gt;resources<\/title><path fill=\"none\" stroke=\"black\" d=\"M318.104,-216.055C324.122,-207.629 331.514,-197.28 338.167,-187.966\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"341.044,-189.961 344.008,-179.789 335.347,-185.892 341.044,-189.961\"><\/polygon><\/g><g id=\"node44\" class=\"node\"><title>nodes<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"377\" cy=\"-450\" rx=\"33.3459\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"377\" y=\"-445.8\" font-family=\"Times,serif\" font-size=\"14.00\">nodes<\/text><\/g><g id=\"edge45\" class=\"edge\"><title>node_details_config-&gt;nodes<\/title><path fill=\"none\" stroke=\"black\" d=\"M256.915,-249.13C178.669,-274.048 39.8385,-329.506 95,-396 124.822,-431.949 262.274,-443.671 333.669,-447.369\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"333.544,-450.867 343.703,-447.858 333.885,-443.875 333.544,-450.867\"><\/polygon><\/g><g id=\"edge48\" class=\"edge\"><title>node_details-&gt;components\/NodeAllocationHeads<\/title><path fill=\"none\" stroke=\"black\" d=\"M602.884,-540.248C605.294,-558.567 610.361,-588.119 620,-612 623.754,-621.301 629.105,-630.802 634.409,-639.144\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"631.624,-641.277 640.068,-647.677 637.458,-637.408 631.624,-641.277\"><\/polygon><\/g><g id=\"edge49\" class=\"edge\"><title>node_details-&gt;components\/NodeDetailsHeader<\/title><path fill=\"none\" stroke=\"black\" d=\"M597.683,-503.849C595.359,-466.832 597.954,-379.181 602.412,-334.386\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"605.914,-334.56 603.551,-324.232 598.958,-333.78 605.914,-334.56\"><\/polygon><\/g><g id=\"edge50\" class=\"edge\"><title>node_details-&gt;node_details_config<\/title><path fill=\"none\" stroke=\"black\" d=\"M598.107,-503.795C591.911,-471.765 574.571,-402.405 535,-360 510.319,-333.552 493.007,-342.633 462,-324 438.529,-309.896 435.432,-302.169 412,-288 392.179,-276.015 369.292,-264.322 349.868,-254.981\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"351.174,-251.726 340.64,-250.593 348.168,-258.048 351.174,-251.726\"><\/polygon><\/g><g id=\"edge51\" class=\"edge\"><title>node_details-&gt;state\/selectors\/node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M613.237,-504.14C626.36,-486.145 647.893,-456.861 667,-432 704.391,-383.35 728.379,-380.203 753,-324 783.497,-254.382 788.693,-163.17 789.246,-118.319\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"792.747,-118.227 789.3,-108.208 785.747,-118.189 792.747,-118.227\"><\/polygon><\/g><g id=\"node15\" class=\"node\"><title>components\/NodeSections<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"996\" cy=\"-378\" rx=\"114.036\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"996\" y=\"-373.8\" font-family=\"Times,serif\" font-size=\"14.00\">components\/NodeSections<\/text><\/g><g id=\"edge52\" class=\"edge\"><title>node_details-&gt;components\/NodeSections<\/title><path fill=\"none\" stroke=\"black\" d=\"M658.947,-517.698C743.317,-511.94 894.845,-497.722 940,-468 962.409,-453.25 977.543,-426.107 986.371,-405.66\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"989.676,-406.825 990.203,-396.243 983.192,-404.187 989.676,-406.825\"><\/polygon><\/g><g id=\"node18\" class=\"node\"><title>components\/NodeSidebar<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"787\" cy=\"-450\" rx=\"110.2\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"787\" y=\"-445.8\" font-family=\"Times,serif\" font-size=\"14.00\">components\/NodeSidebar<\/text><\/g><g id=\"edge53\" class=\"edge\"><title>node_details-&gt;components\/NodeSidebar<\/title><path fill=\"none\" stroke=\"black\" d=\"M633.091,-506.834C659.969,-496.19 698.819,-481.504 730.805,-469.865\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"732.278,-473.054 740.49,-466.359 729.895,-466.472 732.278,-473.054\"><\/polygon><\/g><g id=\"edge54\" class=\"edge\"><title>node_details-&gt;node_filter<\/title><path fill=\"none\" stroke=\"black\" d=\"M660.114,-519.432C739.781,-515.929 875.208,-504.604 906,-468 916.3,-455.756 911.8,-446.912 906,-432 898.131,-411.771 881.685,-415.892 873,-396 842.77,-326.761 846.457,-235.017 850.72,-190.108\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"854.219,-190.301 851.768,-179.994 847.256,-189.58 854.219,-190.301\"><\/polygon><\/g><g id=\"edge55\" class=\"edge\"><title>node_details-&gt;nodes<\/title><path fill=\"none\" stroke=\"black\" d=\"M558.58,-509.362C516.533,-496.854 452.995,-477.021 413.233,-464.007\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"414.257,-460.659 403.664,-460.854 412.067,-467.308 414.257,-460.659\"><\/polygon><\/g><g id=\"edge80\" class=\"edge\"><title>state\/selectors\/node_details-&gt;node_filter<\/title><path fill=\"none\" stroke=\"black\" d=\"M810.451,-107.789C819.334,-116.363 829.377,-126.958 837.662,-136.431\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"835,-138.703 844.144,-144.055 840.333,-134.169 835,-138.703\"><\/polygon><\/g><g id=\"node11\" class=\"node\"><title>components\/NodeSection<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"994\" cy=\"-306\" rx=\"109.903\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"994\" y=\"-301.8\" font-family=\"Times,serif\" font-size=\"14.00\">components\/NodeSection<\/text><\/g><g id=\"node14\" class=\"node\"><title>components\/NodeSubsection<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"359\" cy=\"-18\" rx=\"123.521\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"359\" y=\"-13.8\" font-family=\"Times,serif\" font-size=\"14.00\">components\/NodeSubsection<\/text><\/g><g id=\"edge11\" class=\"edge\"><title>components\/NodeSection-&gt;components\/NodeSubsection<\/title><path fill=\"none\" stroke=\"black\" d=\"M993.946,-287.729C992.781,-243.61 983.02,-126.416 914,-72 881.214,-46.1511 640.523,-31.0705 487.061,-23.9847\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"486.774,-20.468 476.625,-23.5089 486.455,-27.4608 486.774,-20.468\"><\/polygon><\/g><g id=\"edge20\" class=\"edge\"><title>components\/NodeSubsection-&gt;node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M260.208,-28.8668C187.895,-37.5504 97.0395,-51.9495 65,-72 23.4786,-97.9843 0,-112.018 0,-161 0,-379 0,-379 0,-379 0,-430.159 27.9073,-443.835 73,-468 150.6,-509.585 406.634,-518.618 531.415,-520.526\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"531.572,-524.028 541.62,-520.67 531.671,-517.029 531.572,-524.028\"><\/polygon><\/g><g id=\"edge14\" class=\"edge\"><title>components\/NodeSections-&gt;components\/NodeSection<\/title><path fill=\"none\" stroke=\"black\" d=\"M995.506,-359.697C995.285,-351.983 995.02,-342.712 994.775,-334.112\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"998.273,-334 994.489,-324.104 991.276,-334.2 998.273,-334\"><\/polygon><\/g><g id=\"edge17\" class=\"edge\"><title>components\/NodeSidebar-&gt;node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M750.191,-467.027C721.31,-478.384 680.987,-493.56 649.492,-504.927\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"648.254,-501.652 640.023,-508.324 650.618,-508.241 648.254,-501.652\"><\/polygon><\/g><g id=\"node23\" class=\"node\"><title>containers\/EditNode<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"436\" cy=\"-378\" rx=\"90.1208\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"436\" y=\"-373.8\" font-family=\"Times,serif\" font-size=\"14.00\">containers\/EditNode<\/text><\/g><g id=\"edge23\" class=\"edge\"><title>containers\/EditNode-&gt;node_details_config<\/title><path fill=\"none\" stroke=\"black\" d=\"M428.994,-359.754C420.614,-340.629 405.153,-309.628 385,-288 373.64,-275.808 358.962,-264.994 345.347,-256.365\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"346.957,-253.247 336.599,-251.017 343.306,-259.219 346.957,-253.247\"><\/polygon><\/g><g id=\"node26\" class=\"node\"><title>state\/actions\/nodes<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"293\" cy=\"-306\" rx=\"83.4719\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"293\" y=\"-301.8\" font-family=\"Times,serif\" font-size=\"14.00\">state\/actions\/nodes<\/text><\/g><g id=\"edge24\" class=\"edge\"><title>containers\/EditNode-&gt;state\/actions\/nodes<\/title><path fill=\"none\" stroke=\"black\" d=\"M403.551,-361.116C383.074,-351.092 356.432,-338.05 334.433,-327.282\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"335.841,-324.074 325.32,-322.821 332.763,-330.361 335.841,-324.074\"><\/polygon><\/g><g id=\"edge77\" class=\"edge\"><title>state\/actions\/nodes-&gt;node_details_config<\/title><path fill=\"none\" stroke=\"black\" d=\"M296.213,-287.697C297.646,-279.983 299.368,-270.712 300.965,-262.112\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"304.439,-262.575 302.823,-252.104 297.556,-261.297 304.439,-262.575\"><\/polygon><\/g><g id=\"node27\" class=\"node\"><title>containers\/NewNode<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"197\" cy=\"-378\" rx=\"92.3217\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"197\" y=\"-373.8\" font-family=\"Times,serif\" font-size=\"14.00\">containers\/NewNode<\/text><\/g><g id=\"edge27\" class=\"edge\"><title>containers\/NewNode-&gt;node_details_config<\/title><path fill=\"none\" stroke=\"black\" d=\"M192.713,-359.743C188.995,-340.61 186.142,-309.601 200,-288 210.265,-272 226.927,-260.646 244.032,-252.668\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"245.74,-255.743 253.542,-248.575 242.973,-249.313 245.74,-255.743\"><\/polygon><\/g><g id=\"edge28\" class=\"edge\"><title>containers\/NewNode-&gt;state\/actions\/nodes<\/title><path fill=\"none\" stroke=\"black\" d=\"M219.751,-360.411C232.424,-351.17 248.379,-339.536 262.161,-329.487\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"264.395,-332.19 270.413,-323.47 260.27,-326.534 264.395,-332.19\"><\/polygon><\/g><g id=\"edge31\" class=\"edge\"><title>containers\/NodeAllocations-&gt;node_filter<\/title><path fill=\"none\" stroke=\"black\" d=\"M1066.74,-218.155C1018.72,-205.704 950.783,-188.092 904.699,-176.144\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"905.483,-172.732 894.925,-173.61 903.726,-179.508 905.483,-172.732\"><\/polygon><\/g><g id=\"edge58\" class=\"edge\"><title>node_filter-&gt;state\/selectors\/node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M833.49,-145.116C824.542,-136.544 814.285,-125.766 805.793,-116.089\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"808.295,-113.629 799.142,-108.292 802.97,-118.172 808.295,-113.629\"><\/polygon><\/g><g id=\"node34\" class=\"node\"><title>containers\/Resources<\/title><ellipse fill=\"none\" stroke=\"black\" cx=\"359\" cy=\"-90\" rx=\"93.1301\" ry=\"18\"><\/ellipse><text text-anchor=\"middle\" x=\"359\" y=\"-85.8\" font-family=\"Times,serif\" font-size=\"14.00\">containers\/Resources<\/text><\/g><g id=\"edge34\" class=\"edge\"><title>containers\/Resources-&gt;components\/NodeSubsection<\/title><path fill=\"none\" stroke=\"black\" d=\"M359,-71.6966C359,-63.9827 359,-54.7125 359,-46.1124\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"362.5,-46.1043 359,-36.1043 355.5,-46.1044 362.5,-46.1043\"><\/polygon><\/g><g id=\"edge70\" class=\"edge\"><title>processes-&gt;node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M135.215,-177.406C99.1476,-199.682 38,-246.129 38,-305 38,-379 38,-379 38,-379 38,-479.942 382.568,-509.853 532.58,-518.093\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"532.434,-521.59 542.606,-518.627 532.807,-514.6 532.434,-521.59\"><\/polygon><\/g><g id=\"edge71\" class=\"edge\"><title>processes-&gt;process_allocations<\/title><path fill=\"none\" stroke=\"black\" d=\"M155.122,-144.055C154.304,-136.346 154.061,-127.027 154.395,-118.364\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"157.894,-118.491 155.087,-108.275 150.911,-118.012 157.894,-118.491\"><\/polygon><\/g><g id=\"edge67\" class=\"edge\"><title>process_allocations-&gt;processes<\/title><path fill=\"none\" stroke=\"black\" d=\"M166.913,-108.275C167.715,-116.03 167.94,-125.362 167.591,-134.005\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"164.094,-133.832 166.878,-144.055 171.077,-134.327 164.094,-133.832\"><\/polygon><\/g><g id=\"edge74\" class=\"edge\"><title>resources-&gt;containers\/Resources<\/title><path fill=\"none\" stroke=\"black\" d=\"M356.742,-143.697C357.072,-135.983 357.469,-126.712 357.838,-118.112\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"361.335,-118.245 358.267,-108.104 354.342,-117.945 361.335,-118.245\"><\/polygon><\/g><g id=\"edge61\" class=\"edge\"><title>nodes-&gt;node_details<\/title><path fill=\"none\" stroke=\"black\" d=\"M406.367,-458.79C444.171,-469.815 509.973,-490.236 554.55,-504.668\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"553.687,-508.068 564.279,-507.834 555.852,-501.411 553.687,-508.068\"><\/polygon><\/g><g id=\"edge62\" class=\"edge\"><title>nodes-&gt;containers\/EditNode<\/title><path fill=\"none\" stroke=\"black\" d=\"M390.388,-433.116C397.719,-424.418 406.965,-413.448 415.213,-403.663\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"418.074,-405.699 421.842,-395.797 412.721,-401.188 418.074,-405.699\"><\/polygon><\/g><g id=\"edge63\" class=\"edge\"><title>nodes-&gt;state\/actions\/nodes<\/title><path fill=\"none\" stroke=\"black\" d=\"M363.932,-433.319C355.691,-423.051 345.111,-409.146 337,-396 324.552,-375.826 312.78,-351.641 304.55,-333.577\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"307.646,-331.928 300.36,-324.236 301.259,-334.793 307.646,-331.928\"><\/polygon><\/g><g id=\"edge64\" class=\"edge\"><title>nodes-&gt;containers\/NewNode<\/title><path fill=\"none\" stroke=\"black\" d=\"M350.826,-438.821C323.493,-428.192 279.977,-411.269 246.194,-398.131\"><\/path><polygon fill=\"black\" stroke=\"black\" points=\"247.087,-394.723 236.498,-394.36 244.55,-401.247 247.087,-394.723\"><\/polygon><\/g><\/g><\/svg><p><em style=\"display: block; text-align: center; font-size: 80%;\">Pic. 3. Visualization of the minimal graph using webgraphviz.com<\/em><\/p>\n\n\n\n<p>This type of visual representation is very easy for humans to interpret. There are very short cycles, i.e. <code>node_details &lt;-&gt; components\/NodeSidebar<\/code> and longer ones, but main attractors remain <code>node_details<\/code> and <code>node_details_config<\/code>. As a rule of thumb all edges should point in the same direction, i.e. from top to bottom or from top-left to bottom-right. Any edge that goes the other way is worth looking into. The Graph will be empty when there are no cycles.<\/p>\n\n\n\n<p>The entire script with instruction on how to run it using <code>node<\/code> is available as <a href=\"https:\/\/gist.github.com\/aenain\/17a785e40a870d94f38b4ef374434f62\" target=\"_blank\" rel=\"nofollow noopener\">a gist<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Alternative ways<\/h3>\n\n\n\n<p>If you are not into images, you can use <a href=\"https:\/\/www.npmjs.com\/package\/circular-dependency-plugin\" target=\"_blank\" rel=\"nofollow noopener\">circular-dependency-plugin for webpack<\/a> which prints a list of cycles during compilation of assets. Minimal change is needed in <code>webpack.config.js<\/code> &#8211; just add the plugin to the list of <code>plugins<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">const CircularDependencyPlugin = require('circular-dependency-plugin');\n\n\/\/ webpack config\nconst config = {\n  \/\/ \u2026 omitted for brevity\n  plugins: [\n    \/\/ \u2026 other plugins\n    new CircularDependencyPlugin({\n      \/\/ exclude detection of files based on a RegExp\n      exclude: \/a\\.js|node_modules\/,\n      \/\/ add errors to webpack instead of warnings\n      failOnError: false,\n      \/\/ set the current working directory for displaying module paths\n      cwd: process.cwd(),\n    })\n  ]\n};\n<\/pre>\n\n\n\n<p>It will produce output like the following.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">WARNING in Circular dependency detected:\napp\/assets\/javascripts\/process_allocations.js -&gt; app\/assets\/javascripts\/processes.js -&gt; app\/assets\/javascripts\/process_allocations.js\n<\/pre>\n\n\n\n<p>Another option is to use additional rules for <a href=\"https:\/\/eslint.org\/\" target=\"_blank\" rel=\"nofollow noopener\">eslint<\/a> &#8211; <a href=\"https:\/\/github.com\/zertosh\/eslint-plugin-dependencies\" target=\"_blank\" rel=\"nofollow noopener\"><code>dependencies\/no-cycles<\/code> rule from eslint-plugin-dependencies<\/a> or <a href=\"https:\/\/github.com\/benmosher\/eslint-plugin-import\/blob\/HEAD\/docs\/rules\/no-cycle.md#importno-cycle\" target=\"_blank\" rel=\"nofollow noopener\"><code>import\/no-cycle<\/code> rule from eslint-plugin-import<\/a>. Both do the job, but former is significantly faster for longer cycles and provides output that is easier to analyse &#8211; very similar to the one above from webpack plugin.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Resolving problematic dependencies<\/h2>\n\n\n\n<p>Having found all the issues, let\u2019s talk about ways to address them. Solutions may vary depending on the problem. We have used the following strategies.<br>Move problematic exports to another file preferably terminal one which can be imported from all other places without causing cycles.<br>Introduce event triggers and handlers to loose tight coupling.<br>Invert control by injecting dependencies.<\/p>\n\n\n\n<p>Since it might be vague, let me give you a contrived example of the last strategy. Consider the following example.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default class A {\n  foo() {\n    return 'A:foo:' + this.getB().bar();\n  }\n\n  bar() {\n    return 'A:bar';\n  }\n\n  getB() {\n    if (!this.b) this.b = new B();\n    return this.b;\n  }\n};\n\n\/\/ B.js\nimport A from '.\/A';\n\nexport default class B {\n  foo() {\n    return 'B:foo:' + this.getA().bar();\n  }\n\n  bar() {\n    return 'B:bar';\n  }\n\n  getA() {\n    if (!this.a) this.a = new A();\n    return this.a;\n  }\n};\n\n\/\/ index.js\nimport A from '.\/A';\nimport B from '.\/B';\n\nconsole.log(new A().foo() + new B().foo()); \/\/ A:foo:B:barB:foo:A:bar\n<\/pre>\n\n\n\n<p>Circular dependency can be removed if one of the classes is injected via constructor as shown below.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted lang:javascript\">\/\/ A.js\nimport B from '.\/B';\n\nexport default class A {\n  foo() {\n    return 'A:foo:' + this.getB().bar();\n  }\n\n  bar() {\n    return 'A:bar';\n  }\n\n  getB() {\n    if (!this.b) this.b = new B(this);\n    return this.b;\n  }\n};\n\n\/\/ B.js\nexport default class B {\n  constructor(a) { \/\/ the main change\n    this.a = a;\n  }\n\n  foo() {\n    return 'B:foo:' + this.getA().bar();\n  }\n\n  bar() {\n    return 'B:bar';\n  }\n\n  getA() {\n    return this.a;\n  }\n};\n\n\/\/ index.js\nimport A from '.\/A';\nimport B from '.\/B';\n\nconst a = new A();\nconst b = new B(a);\n\nconsole.log(a.foo() + b.foo()); \/\/ A:foo:A:barB:foo:A:bar\n<\/pre>\n\n\n\n<p>After doing so, you can use one of the methods described above to verify if it helped.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Monitoring dependencies<\/h2>\n\n\n\n<p>When so much effort is put into a problem, it\u2019s a good idea to proactively monitor and prevent it from happening again. We have decided to use <a href=\"https:\/\/www.npmjs.com\/package\/circular-dependency-plugin\" target=\"_blank\" rel=\"nofollow noopener\">circular-dependency-plugin for webpack<\/a>. It\u2019s configured in a way that fails on error &#8211; <code>failOnError: true<\/code>. We use that to get an immediate error on CI while compiling assets for feature specs. Refer to advanced usage section in its README if you need to whitelist some existing cycles. If you prefer to run tests nonetheless, then eslint seems like a better option.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>It has been quite a journey :) Congrats you\u2019ve reached the end. Hopefully you have never encountered such a problem, but if you are here, I guess you already have :) In that case I hope you will find the described approaches helpful and will not stumble across the dreadful error message about missing dependencies ever again. If you have scrolled down here for a quick tip &#8211; you will find the entire script with all the instructions here <a href=\"https:\/\/gist.github.com\/aenain\/17a785e40a870d94f38b4ef374434f62\" target=\"_blank\" rel=\"nofollow noopener\">will give you the gist<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What are circular dependecies? What causes problems when using webpack? Learn how to visualize problematic dependencies and prevent them from happening again.<\/p>\n","protected":false},"author":69,"featured_media":10215,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[364],"tags":[],"coauthors":["Artur Hebda"],"class_list":["post-10190","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-labs"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Labs","link":"https:\/\/railsware.com\/blog?category=labs"}],"post_thumbnails":"https:\/\/railsware.com\/blog\/wp-content\/uploads\/2018\/06\/ES6-v2-illustration.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/10190","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/69"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=10190"}],"version-history":[{"count":26,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/10190\/revisions"}],"predecessor-version":[{"id":18074,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/10190\/revisions\/18074"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/10215"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=10190"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=10190"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=10190"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=10190"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}