-
Notifications
You must be signed in to change notification settings - Fork 47
/
index.js
134 lines (119 loc) · 4.27 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
let path = require('path')
let extend = require('util')._extend
let BASE_ERROR = 'Circular dependency detected:\r\n'
let PluginTitle = 'CircularDependencyPlugin'
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 });
}
for (let module of modules) {
const shouldSkip = (
module.resource == null ||
plugin.options.exclude.test(module.resource) ||
!plugin.options.include.test(module.resource)
)
// skip the module if it matches the exclude pattern
if (shouldSkip) {
continue
}
let maybeCyclicalPathsList = this.isCyclic(module, module, {}, compilation)
if (maybeCyclicalPathsList) {
// allow consumers to override all behavior with onDetected
if (plugin.options.onDetected) {
try {
plugin.options.onDetected({
module: module,
paths: maybeCyclicalPathsList,
compilation: compilation
})
} catch(err) {
compilation.errors.push(err)
}
continue
}
// mark warnings or errors on webpack compilation
let error = new Error(BASE_ERROR.concat(maybeCyclicalPathsList.join(' -> ')))
if (plugin.options.failOnError) {
compilation.errors.push(error)
} else {
compilation.warnings.push(error)
}
}
}
if (plugin.options.onEnd) {
plugin.options.onEnd({ compilation });
}
})
})
}
isCyclic(initialModule, currentModule, seenModules, compilation) {
let cwd = this.options.cwd
// 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 dependency of currentModule.dependencies) {
if (
dependency.constructor &&
dependency.constructor.name === 'CommonJsSelfReferenceDependency'
) {
continue
}
let depModule = null
if (compilation.moduleGraph) {
// handle getting a module for webpack 5
depModule = compilation.moduleGraph.getModule(dependency)
} else {
// handle getting a module for webpack 4
depModule = dependency.module
}
if (!depModule) { continue }
// ignore dependencies that don't have an associated resource
if (!depModule.resource) { continue }
// ignore dependencies that are resolved asynchronously
if (this.options.allowAsyncCycles && dependency.weak) { continue }
// the dependency was resolved to the current module due to how webpack internals
// setup dependencies like CommonJsSelfReferenceDependency and ModuleDecoratorDependency
if (currentModule === depModule) {
continue
}
if (depModule.debugId in seenModules) {
if (depModule.debugId === initialModule.debugId) {
// Initial module has a circular dependency
return [
path.relative(cwd, currentModule.resource),
path.relative(cwd, depModule.resource)
]
}
// Found a cycle, but not for this module
continue
}
let maybeCyclicalPathsList = this.isCyclic(initialModule, depModule, seenModules, compilation)
if (maybeCyclicalPathsList) {
maybeCyclicalPathsList.unshift(path.relative(cwd, currentModule.resource))
return maybeCyclicalPathsList
}
}
return false
}
}
module.exports = CircularDependencyPlugin