forked from vbraun/circular-dependency-plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
180 lines (158 loc) · 5.74 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
let path = require('path')
let extend = require('util')._extend
let BASE_ERROR = 'Circular dependency detected:\r\n'
let PluginTitle = 'CircularDependencyPlugin'
let { isAcyclic } = require('./is-acyclic');
class CircularDependencyPlugin {
constructor(options) {
this.options = extend({
exclude: new RegExp('$^'),
include: new RegExp('.*'),
failOnError: false,
allowAsyncCycles: false,
onDetected: false,
cwd: process.cwd()
}, options)
}
apply(compiler) {
let plugin = this
let cwd = this.options.cwd
compiler.hooks.compilation.tap(PluginTitle, (compilation) => {
compilation.hooks.optimizeModules.tap(PluginTitle, (modules) => {
if (plugin.options.onStart) {
plugin.options.onStart({ compilation });
}
const graph = webpackDependencyGraph(compilation, modules, plugin, this.options);
cycleDetector(graph, this.options, (cycle) => {
// print modules as paths in error messages
const cyclicalPaths = cycle.map(
(module) => path.relative(cwd, module.resource));
// allow consumers to override all behavior with onDetected
if (plugin.options.onDetected) {
try {
plugin.options.onDetected({
module: module,
paths: cyclicalPaths,
compilation: compilation
})
} catch(err) {
compilation.errors.push(err)
}
} else {
// mark warnings or errors on webpack compilation
let error = new Error(BASE_ERROR.concat(cyclicalPaths.join(' -> ')))
if (plugin.options.failOnError) {
compilation.errors.push(error)
} else {
compilation.warnings.push(error)
}
}
});
if (plugin.options.onEnd) {
plugin.options.onEnd({ compilation });
}
})
})
}
}
/**
* Construct the dependency (directed) graph for the given plugin options
*
* Returns the graph as a object { vertices, arrow } where
* - vertices is an array containing all vertices, and
* - arrow is a function mapping vertices to the array of dependencies, that is,
* the head vertex for each graph edge whose tail is the given vertex.
*/
function webpackDependencyGraph(compilation, modules, plugin, options) {
// vertices of the dependency graph are the modules
const vertices = modules.filter((module) =>
module.resource != null &&
!plugin.options.exclude.test(module.resource) &&
plugin.options.include.test(module.resource)
);
// arrow function for the dependency graph
const arrow = (module) => module.dependencies
.filter((dependency) => {
// ignore CommonJsSelfReferenceDependency
if (dependency.constructor &&
dependency.constructor.name === 'CommonJsSelfReferenceDependency') {
return false;
}
// ignore dependencies that are resolved asynchronously
if (options.allowAsyncCycles && dependency.weak) { return false }
return true;
})
.map((dependency) => {
// map webpack dependency to module
if (compilation.moduleGraph) {
// handle getting a module for webpack 5
return compilation.moduleGraph.getModule(dependency)
} else {
// handle getting a module for webpack 4
return dependency.module
}
})
.filter((depModule) => {
if (!depModule) { return false }
// ignore dependencies that don't have an associated resource
if (!depModule.resource) { return false }
// the dependency was resolved to the current module due to how webpack internals
// setup dependencies like CommonJsSelfReferenceDependency and ModuleDecoratorDependency
if (module === depModule) {
return false
}
return true;
});
return { vertices, arrow };
}
/**
* Call the callback with all cycles in the directed graph defined by (vertices, arrow)
*
* The graph is acyclic iff the callback is not called.
*/
function cycleDetector(graph, options, cycleCallback) {
// checking that there are no cycles is much faster than actually listing cycles
if (isAcyclic(graph, options))
return;
/**
* This is the old implementation which has performance issues. In the future, it might
* be replaced with a different implementation. Keep it for now so none of the unit tests change.
*/
for (let module of graph.vertices) {
let cycle = findModuleCycleAt(module, module, {}, graph.arrow)
if (cycle) {
cycleCallback(cycle);
}
}
}
// part of the cycleDetector implementation
function findModuleCycleAt(initialModule, currentModule, seenModules, arrow) {
// Add the current module to the seen modules cache
seenModules[currentModule.debugId] = true
// If the modules aren't associated to resources
// it's not possible to display how they are cyclical
if (!currentModule.resource || !initialModule.resource) {
return false
}
// Iterate over the current modules dependencies
for (let depModule of arrow(currentModule)) {
if (depModule.debugId in seenModules) {
if (depModule.debugId === initialModule.debugId) {
// Initial module has a circular dependency
return [
currentModule,
depModule,
]
}
// Found a cycle, but not for this module
continue
}
let maybeCyclicalPathsList = findModuleCycleAt(initialModule, depModule, seenModules, arrow)
if (maybeCyclicalPathsList) {
maybeCyclicalPathsList.unshift(currentModule);
return maybeCyclicalPathsList;
}
}
return false
}
module.exports = CircularDependencyPlugin