Skip to content

Commit

Permalink
feat: circular plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
easy1090 committed Dec 16, 2024
1 parent 61d234f commit 9dd2e92
Showing 1 changed file with 127 additions and 0 deletions.
127 changes: 127 additions & 0 deletions packages/core/src/rules/rules/circular-dependency/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
export 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
}
}

0 comments on commit 9dd2e92

Please sign in to comment.