-
-
Notifications
You must be signed in to change notification settings - Fork 599
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
feat: implement CircularDependencyRspackPlugin #8876
base: main
Are you sure you want to change the base?
feat: implement CircularDependencyRspackPlugin #8876
Conversation
✅ Deploy Preview for rspack ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify site configuration. |
CodSpeed Performance ReportMerging #8876 will not alter performanceComparing 🎉 Hooray!
|
let mut cycles = vec![]; | ||
self.recurse_dependencies( | ||
initial_module_id, | ||
&mut IdentifierSet::default(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we initialize a new HashSet every time here ?
This is used for reducing dup visit I suppose, should we move this up to the upper call site ?
Assume entry module a and entry module b both depend on module shared, shared is the root of a cycle, if we make seen_set a new HashSet everytime we will end up 2 same cycles
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think re-reporting each cycle for each entrypoint is somewhat useful, purely for the sake of completeness (i.e., you can know that a cycle is encountered in 2 of 5 entrypoints, but not in the other 3, and use the onDetected
hook to report them separately). I don't feel super strongly about that, though.
Summary
This PR implements
CircularDependencyRspackPlugin
, an adaptation ofcircular-dependency-plugin
for Webpack implemented natively in Rust to work with the Rspack module graph.circular-dependency-plugin
is a moderately-used plugin (~500k weekly downloads) to detect circular import dependencies in bundled code. It's not perfect, and the output isn't always meaningful (i.e., import cycles aren't forbidden in JS, so long as initialization can occur without creating a cycle. This plugin cannot detect that, as is), but identifying cycles is helpful for debugging and resolving bugs and errors caused by circular dependencies.There's been an issue requesting support for this plugin for most of this year, but the major blocker for being able to run the existing plugin is exposing the module graph and dependencies on the JS side, which incurs a major serialization cost. One suggestion was to use the existing
stats.json
object to reverse-build the module graph, but that is also error-prone and not easy to parse, alongside still requiring expensive up-front serialization. So, this PR rewrites the plugin natively in Rust, specifically for Rspack's module graph.At the same time, the existing plugin is known to be very slow, and only really feasible to run on production builds rather than in a hot-reloading development load. Instead, the implementation for detecting cycles here has been updated to be far more efficient, requiring only a single traversal of the module graph rather than one traversal per module. With that change, and the memory efficiency of reusing the existing module graph, performance is imperceptible, even for projects with thousands of modules and hundreds of thousands of connections between them (in testing, our codebase has ~20k modules and ~230k connections, and the plugin executes with the JS callback hooks in under 50ms). This implementation means it can easily be used in development mode and hot-reloading environments and provide better feedback in the moment.
Divergence:
This implementation is effectively the same as the webpack version, but it has some differences:
cwd
option also isn't implemented). There isn't a big reason for this other than it being expensive to copy Strings all over the place for comparing with the exclude/connection patterns, and the borrow checker really doesn't like keeping Cow references around when the JS callbacks are invoked with a mutable compilation.include
option is not present, because the original plugin only used this to filter the list of modules it traversed. It can be added without much issue, but doesn't really do much meaningfully.ignoredConnections
to give more granular control over ignoring specific cycles, since ignoring an entire module is often too heavy-handed and would prevent detecting any cycles through that module, which seems antithetical to the intent of the plugin (you can never know absolutely that a module isn't problematically cyclical, you can only guess, at which point the plugin gives you nothing).onIgnored
callback to handle them if desired.onDetected
callback also includes anentrypoint
parameter first indicating which entrypoint the cycle occurred in (the same cycle will be reported for each entrypoint it occurs in).Checklist