What is circular dependency
Circular dependency generally appears with modularization, that is, it depends on module b in module a, and module b depends on module a.
This situation has been encountered in previous development. During the initialization of the store, the methods in the utils module are used, and the data in the store is used in utils
There is no problem during development, but an error is reported when running after packaging, which is actually a problem of circular dependency.
For circular dependency, the default CommonJS module of Node, the module of ES6 and the processing of Webpack are different
Handling of circular dependency in CommonJS
You can see the official pair of nodes Introduction to circular dependency:
a.js, through require, references the b module:
console.log('a start'); exports.done = false; const b = require('./b.js'); console.log('in a, b.done = %j', b.done); exports.done = true; console.log('a done');
In b.js, module a is referenced through require:
console.log('b start'); exports.done = false; const a = require('./a.js'); console.log('in b, a.done = %j', a.done); exports.done = true; console.log('b done');
In main.js, reference modules a and b successively:
console.log('main start'); const a = require('./a.js'); const b = require('./b.js'); console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
Then execute node main.js. What will be the output result
main starting a starting b starting in b, a.done = false b done in a, b.done = true a done in main, a.done = true, b.done = true
When running module a, if you encounter require('b '), run b.js. If you encounter require('a'), in order to avoid circular reference, an unfinished copy of exports of a.js will be returned to B as the result of require('a ')
Therefore, at this time, a.done in B is false. After executing B, continue to execute module a, and b.done of module a becomes true
As can be seen from the above example, the CommonJS module handles circular dependency well, which mainly depends on its two characteristics:
- Modules are loaded at runtime
- Loaded (including incomplete) modules are cached
ESM processing
In a.mjs, obtain the exported bar in b.mjs through import, and b.mjs obtains the foo in a.mjs through import
. mjs is used to identify the use of ES6 module
a. In MJS:
import {bar} from './b.mjs'; console.log('a.mjs'); console.log(bar); export const foo = 'foo';
b. In MJS:
import {foo} from './a.mjs'; console.log('b.mjs'); console.log(foo); export const bar = 'bar';
Then execute in the Node environment:
node --experimental-modules a.mjs
Execution results:
b.mjs console.log(foo); ^ ReferenceError: Cannot access 'foo' before initialization
According to Mr. Ruan Yifeng's explanation, after executing a.mjs, the engine finds that b.mjs is loaded, and then b.mjs is executed first.
When b.mjs is executed, it is found that foo is imported from A. at this time, a.mjs will not be executed. It will be considered that foo already exists and continue to complete the execution. It is not found that foo is not defined at all until it runs to console.log(foo), so an error is reported
If the declaration of the last variable in a.mjs is changed from const to var, because foo has variable promotion, the output result will change and no error will be reported:
b.mjs undefined a.mjs bar
The above results also conform to the characteristics of ESM:
- The ESM module outputs a reference to a value
- Output interface dynamic execution
- Static interface
Webpack's handling of circular dependency
First, Webpack and Webpack cli are installed:
npm install webpack webpack-cli -D
Then, a new webpack.config.js configuration file is created in the project:
const path = require('path'); module.exports = { entry: path.resolve(__dirname, 'demo12/commonjs/index.js'), output: { path: path.resolve(__dirname, 'demo12/dist'), filename: 'my-first-webpack.bundle.js' }, };
After configuring the simplest packaging configuration and running the webpack command configured in package.json:
> webpack asset my-first-webpack.bundle.js 533 bytes [compared for emit] [minimized] (name: main) ./demo12/commonjs/index.js 193 bytes [built] [code generated] ./demo12/commonjs/a.js 203 bytes [built] [code generated] ./demo12/commonjs/b.js 203 bytes [built] [code generated]
Webpack does not detect circular dependency, and there are no errors in the packaging process. The results after packaging in the browser are exactly the same as those in CommonJS
If you need Webpack to detect circular dependency, you need to use the plug-in circular dependency plugin:
npm i circular-dependency-plugin -D
Then add the following configuration in webpack.config.js:
const path = require('path'); const CircularDependencyPlugin = require('circular-dependency-plugin'); module.exports = { entry: path.resolve(__dirname, 'demo12/commonjs/index.js'), output: { path: path.resolve(__dirname, 'demo12/dist'), filename: 'my-first-webpack.bundle.js' }, plugins: [ new CircularDependencyPlugin({ exclude: /node_modules/, include: /demo12/, failOnError: true, allowAsyncCycles: false, cwd: process.cwd() }) ] };
After packaging, the plug-in detects the circular dependency and fails the packaging according to our configuration: