Skip to content

Commit

Permalink
feat: circular dependency plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
easy1090 committed Dec 25, 2024
1 parent 7536a8c commit 4428d14
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 47 deletions.
2 changes: 1 addition & 1 deletion examples/modern-minimal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "@rsdoctor/tsconfig/base",
"include": ["src"],
"include": ["src", "../webpack-minimal/src/deps"],
"compilerOptions": {
"outDir": "dist",
"baseUrl": ".",
Expand Down
2 changes: 0 additions & 2 deletions examples/rspack-layers-minimal/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ function rspackBuild(config: rspack.Configuration) {
throw err;
}

console.log();

if (stats) {
console.log(
stats.toString({
Expand Down
1 change: 0 additions & 1 deletion packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export async function execute(
await action(args);
} catch (error) {
const { message, stack } = error as Error;
console.log('');
console.error(red(stack || message));
process.exit(1);
}
Expand Down
1 change: 0 additions & 1 deletion packages/components/src/utils/file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ export function readJSONByFileReader<T extends Common.PlainObject>(file: unknown
const reader = new FileReader();
reader.onloadend = () => {
const { result } = reader;
console.log('reader result: ', result);
try {
const json = JSON.parse(result!.toString());
resolve(json);
Expand Down
2 changes: 2 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@
"@rsdoctor/sdk": "workspace:*",
"@rsdoctor/types": "workspace:*",
"@rsdoctor/utils": "workspace:*",
"@types/circular-dependency-plugin": "^5.0.8",
"axios": "^1.7.9",
"circular-dependency-plugin": "^5.2.2",
"enhanced-resolve": "5.12.0",
"filesize": "^10.1.6",
"fs-extra": "^11.1.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import path from 'path';
let extend = require('util')._extend;

let BASE_ERROR = 'Circular dependency detected:\r\n'
let PluginTitle = 'CircularDependencyPlugin'

export class CircularDependencyPlugin {
constructor(options) {
options: {
exclude: RegExp;
include: RegExp;
failOnError: boolean;
allowAsyncCycles: boolean;
onDetected?: (data: { module: any; paths: string[]; compilation: any }) => void;
onStart?: (data: { compilation: any }) => void;
onEnd?: (data: { compilation: any }) => void;
cwd: string;
};

constructor(options: Partial<typeof this.options>) {
this.options = extend({
exclude: new RegExp('$^'),
include: new RegExp('.*'),
failOnError: false,
allowAsyncCycles: false,
onDetected: false,
cwd: process.cwd()
}, options)
}, options);
}

apply(compiler) {
let plugin = this
let cwd = this.options.cwd
apply(compiler: any) {
let plugin = this;

compiler.hooks.compilation.tap(PluginTitle, (compilation) => {
compilation.hooks.optimizeModules.tap(PluginTitle, (modules) => {
compiler.hooks.compilation.tap(PluginTitle, (compilation: any) => {
compilation.hooks.optimizeModules.tap(PluginTitle, (modules: any[]) => {
if (plugin.options.onStart) {
plugin.options.onStart({ compilation });
}
Expand All @@ -24,54 +40,56 @@ export class CircularDependencyPlugin {
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
continue;
}

let maybeCyclicalPathsList = this.isCyclic(module, module, {}, compilation)
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
})
if (Array.isArray(maybeCyclicalPathsList)) {
plugin.options.onDetected({
module: module,
paths: maybeCyclicalPathsList,
compilation: compilation
});
}
} catch(err) {
compilation.errors.push(err)
compilation.errors.push(err);
}
continue
continue;
}

// mark warnings or errors on webpack compilation
let error = new Error(BASE_ERROR.concat(maybeCyclicalPathsList.join(' -> ')))
let error = maybeCyclicalPathsList && typeof maybeCyclicalPathsList === 'object' && maybeCyclicalPathsList.length && new Error(BASE_ERROR.concat(maybeCyclicalPathsList.join(' -> ')));
if (plugin.options.failOnError) {
compilation.errors.push(error)
compilation.errors.push(error);
} else {
compilation.warnings.push(error)
compilation.warnings.push(error);
}
}
}
if (plugin.options.onEnd) {
plugin.options.onEnd({ compilation });
}
})
})
});
});
}

isCyclic(initialModule, currentModule, seenModules, compilation) {
let cwd = this.options.cwd
isCyclic(initialModule: any, currentModule: any, seenModules: { [key: string]: boolean }, compilation: any): string[] | undefined | boolean {
let cwd = this.options.cwd;

// Add the current module to the seen modules cache
seenModules[currentModule.debugId] = true
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
return false;
}

// Iterate over the current modules dependencies
Expand All @@ -80,27 +98,27 @@ export class CircularDependencyPlugin {
dependency.constructor &&
dependency.constructor.name === 'CommonJsSelfReferenceDependency'
) {
continue
continue;
}

let depModule = null
let depModule: any = null;
if (compilation.moduleGraph) {
// handle getting a module for webpack 5
depModule = compilation.moduleGraph.getModule(dependency)
depModule = compilation.moduleGraph.getModule(dependency);
} else {
// handle getting a module for webpack 4
depModule = dependency.module
depModule = dependency.module;
}

if (!depModule) { continue }
if (!depModule) { continue; }
// ignore dependencies that don't have an associated resource
if (!depModule.resource) { continue }
if (!depModule.resource) { continue; }
// ignore dependencies that are resolved asynchronously
if (this.options.allowAsyncCycles && dependency.weak) { continue }
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
continue;
}

if (depModule.debugId in seenModules) {
Expand All @@ -109,19 +127,19 @@ export class CircularDependencyPlugin {
return [
path.relative(cwd, currentModule.resource),
path.relative(cwd, depModule.resource)
]
];
}
// Found a cycle, but not for this module
continue
continue;
}

let maybeCyclicalPathsList = this.isCyclic(initialModule, depModule, seenModules, compilation)
let maybeCyclicalPathsList: any = this.isCyclic(initialModule, depModule, seenModules, compilation);
if (maybeCyclicalPathsList) {
maybeCyclicalPathsList.unshift(path.relative(cwd, currentModule.resource))
return maybeCyclicalPathsList
maybeCyclicalPathsList.unshift(path.relative(cwd, currentModule.resource));
return maybeCyclicalPathsList;
}
}

return false
return false;
}
}
}
10 changes: 10 additions & 0 deletions packages/core/src/inner-plugins/plugins/rule-plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CircularDependencyPlugin } from "./circular-dependency";

export const circularDependencyPlugin = new CircularDependencyPlugin({
// 在这里添加所需的选项
exclude: /node_modules/,
include: /src/,
failOnError: true,
allowAsyncCycles: false,
cwd: process.cwd(),
});
4 changes: 4 additions & 0 deletions packages/core/src/inner-plugins/plugins/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { DevToolError } from '@rsdoctor/utils/error';
import { isArray, pull } from 'lodash';
import { Plugin } from '@rsdoctor/types';
import { WebpackError } from 'webpack';
import { circularDependencyPlugin } from './rule-plugins';

export class InternalRulesPlugin extends InternalBasePlugin<Plugin.BaseCompiler> {
public readonly name = 'rules';

public apply(compiler: Plugin.BaseCompiler) {
compiler.hooks.done.tapPromise(this.tapPreOptions, this.done);

// Execute rules plugins.
circularDependencyPlugin.apply(compiler);
}

public done = async (stats: Plugin.BaseStats): Promise<void> => {
Expand Down
29 changes: 28 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4428d14

Please sign in to comment.