Make writing a habit together! This is the second day of my participation in the “Gold Digging Day New Plan · April More text challenge”. Click here for more details.


background

Some time ago, WHEN I was making requirements, I stepped into a pit. In the VUE project, the router I imported was actually undefined. At that time, I thought I made some stupid mistake and wrote the exported variable wrong. It turned out that the problem was caused by cyclic loading.

circular-dependency-plugin

Now that we’ve identified the problem, we can fix it by tracing the cause, using the circular-dependency plugin, which throws a circular load link at build time.

// webpack.config.js
const CircularDependencyPlugin = require('circular-dependency-plugin')
 
module.exports = {
  plugins: [
    new CircularDependencyPlugin({
      // exclude detection of files based on a RegExp
      exclude: /a.js|node_modules/,
      // include specific files based on a RegExp
      include: /dir/,
      // add errors to webpack instead of warnings
      failOnError: true,
      // allow import cycles that include an asyncronous import,
      // e.g. via import(/* webpackMode: "weak" */ './file.js')
      allowAsyncCycles: false,
      // set the current working directory for displaying module paths
      cwd: process.cwd(),
    })
  ]
}
Copy the code

After this is added to the configuration, an error like this will be thrown in the console if there is a cyclic load in the project, and then the link will be disconnected according to the respective business scenario.

ERROR  Circular dependency detected:
src\b.js -> src\a.js -> src\b.js
Copy the code

With that out of the way, we’ll have to look at why and why.

What is cyclic loading

Circular dependency means that script A’s execution depends on script B, which in turn depends on script A, or a longer link.

Cyclic loading itself represents a strong coupling between modules in a project, and problems like the ones mentioned above can occur when handled poorly, but this is a very difficult situation to avoid, especially in large and complex projects, so the module loading mechanism must take this into account. The two most common module formats for JavaScript today, CommonJS and ES6, are handled differently.

CommonJS loop loading

An important feature of CommonJS is that the module will be executed when require. If the module is loop-loaded, only the part that has been executed will be output.

Let’s look at the following code execution:

// a.js exports.done = false; var b = require('./b.js'); Console. log(' in a.js, b.tone = %j', b.tone); console.log(' in a.js, b.tone = %j', b.tone); exports.done = true; Console. log(' A.js finished '); // b.js exports.done = false; var a = require('./a.js'); Console. log(' in b.js, a.tone = %j', a.tone); exports.done = true; Console. log(' B.js finished '); //main.js var a = require('./a.js'); var b = require('./b.js'); Console. log(' in main.js, a.tone =%j, b.tone =%j', a.tone, b.tone);Copy the code

The order of execution is as follows:

  1. inmain.jsIn the loada.jsAnd then executea.jsCode in;
  2. inexports.done = false;After that, it loads againb.js, so it is executed firstb.js
  3. inb.jsIn, execute in order, only up tovar a = require('./a.js');“, you can only get the previous contenta.jsThe first line of code executed in, i.eexports.done = false;And so onb.jsIn the loada.doneThe value offalse;
  4. afterb.jsAfter, back toa.jsExecute the previously unexecuted portion, that is, fromvar b = require('./b.js');I’m going to go ahead and start executing, and now B is completely finished, sob.donetrueUntil the rest is executed;
  5. Executive power continues to returnmain.jsAt this time, var a = require('./a.js');We’re finally done. Let’s start the second linevar b = require('./b.js');, but it will not be re-executedb.js, but output before executionb.jsCache results untilmain.jsThe execution is complete.

After executing main.js, the following output is displayed:

In b.js, a.one = false, b.One =true. In main. Js, a.One =true, b.One =trueCopy the code

So under CommonJS, when you run a looping load, the output is the partial value, not the total value.

Cyclic loading of ES6 modules

Unlike CommonJS, which prints a copy of a value, ES6 prints a reference to the value. So the result of execution is not cached.

Looking at an example of an ES6 module, we first execute the code for module Foo:

// foo
console.log('foo is running');
import {bar} from './bar'
console.log('bar = %j', bar);
setTimeout(() => console.log('bar = %j after 500 ms', bar), 500);
export let foo = false;
console.log('foo is finished');

// bar
console.log('bar is running');
import {foo} from './foo';
console.log('foo = %j', foo)
export let bar = false;
setTimeout(() => bar = true, 500);
console.log('bar is finished');
Copy the code

The order of execution is as follows:

  1. performfooModule, due toimportThe lifting effect, the first entrybarModule;
  2. First, the outputbar is running, the rest are executed in turn;
  3. Return of executive powerfooModule, executed firstconsole.log('foo is running');And then to performconsole.log('bar = %j', bar);Because of the second sentenceimportThe statement has already been executed), and the rest of the statements are executed;
  4. inbarThe timer is set in the module, and will be changed after 500 msbar = trueAnd so onfooModule, output after 500 msbarThe value of has changed totrue, is no longerfalse;

So the result looks like this:

bar is running
foo = undefined
bar is finished
foo is running
bar = false
foo is finished
bar = true after 500 ms
Copy the code

When we run the foo module first, why not print foo is running first instead of bar is running first? Because the import command is promoted to the header of the entire module, it is executed first. The essence of this behavior is that the import command is executed at compile time, before the code runs.

Second, if a dependency loop is found, instead of continuing to load the dependency, it is assumed that it already exists and continues to execute. For example, if foo = undefined is output, you can see that the module has been imported normally. Why can’t you get the value? These are some of the problems with circular dependencies, and it is recommended to use the circular-dependency plugin to find such problems.

conclusion

Cyclic loading is difficult to avoid in some large and complex projects, and it is difficult to troubleshoot problems. However, we can pay more attention to it in normal development. With this understanding, we can solve problems faster when we encounter such problems.

The above is my shallow understanding, if there is wrong also hope you correct.