ES6 Cyclic Module Loading
Before we get into the differences between ES5 and ES6 handling of cyclic dependencies, let’s first talk about what that means because it isn’t the most common thing and is something I have only recently learned about as well.
Cyclical dependencies means that you have 2 files which imports one another. A.js imports B.js, and B.js imports A.js . This isn’t a design pattern that is recommended however as it leads to tightly coupled code.
Now that we know what it means to have cyclic dependencies, let’s get into the difference between ES5 and ES6. I will be referring to CommonJS method for handling modules in ES5.
With ES6 modules the way cyclic dependencies are handled has changed slightly. There are 3 main differences outlined here:
- Bindings are set up before module execution.
- Execution is run from depth-first left to right on the module tree stopping at circular references.
- Bindings are live — an adjustment to an export of one module affects all modules importing it, but it can only be modified in the defining module.
After playing around with trying to break things for a while I was only able to recreate a good example of the last point. That is, that you can no longer modify the import. Any mutation to that import must be done in its original file.
In the example below I show how in ES6 I am able to increment a counter defined in the subtract.js file, however doing so in the example with ES6 modules, causes an error.
In the example below I outline how you are able to modify variables outside of their original file with ES5. Trying to replicate that mutation on the import variable throws an error using ES6 modules.
ES5 Import Mutation Example
Here I’m creating a file called subtract.js In this file I import the add.js file and increment countSubtract inside the subtractfn function. In the subtractfn method I am returning a callback to a method from add.js
Here I’m creating a file called add.js In this file I import the subtract.js file and I also increment the countSubtract variable inside the add function. I am able to mutate the countSubtract import which is defined in a different file.
This is the log when it is executed. It’s a bit jumbled together but look closely at countSubtract. It finishes at 21 while countAdd which is only incremented in one file ends at 11. We were able to modify this import outside of the file it was defined in.
Now Let’s take a look at how ES6 handles mutating imports.
Again this is the same code as above, but with ES6 module syntax. I’m creating a file called subtract.js which imports modules from add.js In here I am modifying the countSubtract variable and there are no problems.
Again with the add.js file I import subtract.js and attempt to increment the countSubtract variable.
However unlike the previous example, this time we get an error immediately which states “countSubtract” is read-only.
The variable is recognized as an import and is not able to be changed.
After commenting that line out I ran the same code again which produced the follow result.
Here we can see the value countSubtract is still accessible in the add function. The results are just as we expect, the countSubract value can’t be modified anywhere which makes things more predictable and easier to work with.
One thing that has not changed between the two ECMAScript versions, is you still cannot access imports in the body of the module. An example of that is importing a variable with an integer value. In the Gists above you can see I still had access to countSubtract inside the add function, however using it outside that function returned undefined.
This was a great learning experience for me as I got a little more under the hood on how things work and read up on the spec and definitions that go along with it here https://tc39.github.io/ecma262/#sec-modules