An overview of the various scenarios
| Scenario | type of export | target | (in)direct | error | hoisting | module.exports of circular module |
|---|---|---|---|---|---|---|
| regular | default | es5 | direct | - | no | {} |
| regular es6 | default | es6 | direct | y1 | no | { default: getterFn() } |
| regular es6 var | default | es6 | direct | y1 | yes | { default: getterFn() } |
| regular indirect | default | es5 | indirect, functions | y2 | no | { default: expectedFn() } |
| regular named exports | named | es5 | direct | y3 | no | { [namedExportName]: getterFn() } |
| named exports es6 | named | es6 | direct | y3 | no | { [namedExportName]: getterFn() } |
| named indirect | named | es5 | indirect, function | possibly4 | no | { [namedExportName]: getterFn() } |
| class instance regular | default | es5 | direct | y5 | no | {} |
| class instance named | default | es5 | direct | y3 | no | { [namedExportName]: getterFn() } |
| class instance indirect named | named | es5 | indirect | possibly4 | no | { [namedExportName]: getterFn() } |
| regular chunks entryPoints | default | es5 | direct | - | no | {} |
| regular chunks dynamic import | default | es5 | direct | - | no | {} |
| named chunks dynamic import | named | es5 | indirect dynamic import | possibly4 | no | { [namedExportName]: getterFn() } |
| named chunks dynamic import ESM | named | es66 | indirect dynamic import | possibly4 | no | { [namedExportName]: getterFn() } |
Uncaught ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
Maximum call stack size exceeded
Uncaught ReferenceError: Cannot access '{{namedExportName}}' before initialization
depends on which named export is being called, if it returns a static value, no error or issues. If it calls a module within the circ dep chain, it will lead to Maximum call stack size exceeded
cannot read {{propertyName}} of undefined. The issue here is that the class is not exported yet, due to preliminary module.exports = {}
also setting output.module = true and experiments.outputModule = true
default vs namedWhat kind of ES Module export statement are we using.
Default: export default
Named: export myVar or export { foo: varFoo, bar: varBar }
This is important because circular dependencies with default exports are undefined when reading directly and using es5.
This can lead to unexpected behavior, but will less likely lead to app-crashing errors.
Circular dependencies with named exports use getters and when used directly, will lead to
Uncaught ReferenceError: Cannot access '{{namedExportName}}' before initialization errors which can crash your app if not caught properly.
Which Webpack output target are we setting?
The main difference here is that with es6 and later, default exports are treated as named exports, using a getter.
This can lead to Uncaught ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
Are all modules processed by Webpack before reading imported values?
Important note: indirect is not the same as async. This is all about the synchronous order of execution due to importing of scripts.
The moment we access an imported value matters:
If we access a value from a circular module before that module is processed fully, it's exports will not be set yet. It will have the preliminary module.exports = {}.
However, if we read value(s) from a circular module until after all modules in the circ dep chain are finished, we are able to read from the module.exports with its default or named exports set correctly.
The finishing part is important because webpack holds a reference to a module's module.exports and that can change once processing all modules is done:
In the above example, by the time that we call indirectFn() in entry.js all modules have been fully processed by Webpack
and have their module.exports set to the correct value.
It is important to realize that module.exports for moduleA.js is different when reading it in moduleB.js in direct,
vs reading it in entry.js after all imports have been resolved.
Hopefully these pages have given you a better understanding of the way circular dependencies are handled in Webpack.
And perhaps you've also learned a couple things about the module resolution system.
Here are some key take aways:
__webpack_module_cache__module.exports from the __webpack_module_cache__module.exports set to an empty object ({})module.exports
es6 as target, the default export is treated as named export, with a getter for __WEBPACK_DEFAULT_EXPORT__module.exports = {} even before processing the module
import/require of itselfbabel, jest, typescript) in your project might handle circular dependencies differently...
There's no next, but you can return to the first page