Exporting functions

This example assumes you have followed along with:

  1. the basics
  2. the default export (es5) example
  3. the named exports example
  4. the default export (es6) example

The outcome of the two scenarios covered below are valid for both es5 and es6.

By now you should have gotten a good grasp on the key elements at play here:

So what happens if you do not directly access value but use functions to ensure all modules are loaded cached, before reading values? Should that help with the getters?

Let's find out

Every default export is a function

Given these source files:

entry.js
moduleA.js
moduleC.js
moduleB.js

_10
import foo from './moduleA';
_10
_10
console.log(foo());

Will we end up in an infinite loop?

Yes.

Full file

Second require of moduleA.js

As all exports are functions, and none of the modules is immediately invoking an imported function, we are safely reaching line 55.

All modules have their module.exports.default set to the correct function.

But in this case that mean mayhem:

moduleA calls moduleC which calls moduleB which calls moduleA into infinity.

Maximum call stack size exceeded
dist/main.js

_58
(function () {
_58
"use strict";
_58
var __webpack_modules__ = ({
_58
"./src/regular indirect/moduleA.js":
_58
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
_58
__webpack_require__.r(__webpack_exports__);
_58
var _moduleC__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect/moduleC.js");
_58
const foo = () => (0, _moduleC__WEBPACK_IMPORTED_MODULE_0__["default"])() + 'bar';
_58
__webpack_exports__["default"] = (foo);
_58
}),
_58
"./src/regular indirect/moduleB.js":
_58
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
_58
__webpack_require__.r(__webpack_exports__);
_58
var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect/moduleA.js");
_58
const bar = () => (0, _moduleA__WEBPACK_IMPORTED_MODULE_0__["default"])() + 'bar';
_58
__webpack_exports__["default"] = (bar);
_58
}),
_58
"./src/regular indirect/moduleC.js":
_58
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
_58
__webpack_require__.r(__webpack_exports__);
_58
var _moduleB__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect/moduleB.js");
_58
const baz = () => (0, _moduleB__WEBPACK_IMPORTED_MODULE_0__["default"])() + 'baz';
_58
__webpack_exports__["default"] = (baz);
_58
})
_58
});
_58
_58
var __webpack_module_cache__ = {};
_58
_58
function __webpack_require__(moduleId) {
_58
var cachedModule = __webpack_module_cache__[moduleId];
_58
if (cachedModule !== undefined) {
_58
return cachedModule.exports;
_58
}
_58
var module = __webpack_module_cache__[moduleId] = {
_58
exports: {}
_58
};
_58
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
_58
return module.exports;
_58
}
_58
_58
!function () {
_58
__webpack_require__.r = function (exports) {
_58
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
_58
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
_58
}
_58
Object.defineProperty(exports, '__esModule', { value: true });
_58
};
_58
}();
_58
_58
var __webpack_exports__ = {};
_58
_58
!function () {
_58
__webpack_require__.r(__webpack_exports__);
_58
var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect/moduleA.js");
_58
console.log((0, _moduleA__WEBPACK_IMPORTED_MODULE_0__["default"])());
_58
}();
_58
})()
_58
;

Use named exports in moduleA

Let's now create a variation where moduleA exports two named functions:

moduleA.js
moduleC.js
moduleB.js
entry.js

_10
import c from './moduleC';
_10
_10
const namedA = () => 'a';
_10
const namedWithImportedValue = () => c()+'b';
_10
_10
export {
_10
namedA,
_10
namedWithImportedValue,
_10
}

Let's see what happens now:

Full file

Breaking the loop

Like in the previous example, we safely reach line 73 as we do not invoke any of the functions while requiring other files.

All modules have their module.exports correctly set and now we call moduleA.namedWithImportedValue.

This time we do not end up in infinite loop as moduleB uses a function from moduleA which returns a static value.

Depending on yourself

Console.log output from entry.js

_10
{
_10
namedA: "a",
_10
namedWithImportedValue: "abarbazb",
_10
}

In this scenario, it is save for moduleA to depend on itself!
dist/main.js

_76
(function () {
_76
"use strict";
_76
var __webpack_modules__ = ({
_76
"./src/regular indirect named export/moduleA.js":
_76
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
_76
__webpack_require__.r(__webpack_exports__);
_76
__webpack_require__.d(__webpack_exports__, {
_76
"namedA": function () { return namedA; },
_76
"namedWithImportedValue": function () { return namedWithImportedValue; }
_76
});
_76
var _moduleC__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect named export/moduleC.js");
_76
const namedA = () => 'a';
_76
const namedWithImportedValue = () => (0, _moduleC__WEBPACK_IMPORTED_MODULE_0__["default"])() + 'b';
_76
}),
_76
"./src/regular indirect named export/moduleB.js":
_76
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
_76
__webpack_require__.r(__webpack_exports__);
_76
var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect named export/moduleA.js");
_76
const bar = () => _moduleA__WEBPACK_IMPORTED_MODULE_0__.namedA() + 'bar';
_76
__webpack_exports__["default"] = (bar);
_76
}),
_76
"./src/regular indirect named export/moduleC.js":
_76
(function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
_76
__webpack_require__.r(__webpack_exports__);
_76
var _moduleB__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect named export/moduleB.js");
_76
const baz = () => (0, _moduleB__WEBPACK_IMPORTED_MODULE_0__["default"])() + 'baz';
_76
__webpack_exports__["default"] = (baz);
_76
})
_76
});
_76
_76
var __webpack_module_cache__ = {};
_76
_76
function __webpack_require__(moduleId) {
_76
var cachedModule = __webpack_module_cache__[moduleId];
_76
if (cachedModule !== undefined) {
_76
return cachedModule.exports;
_76
}
_76
var module = __webpack_module_cache__[moduleId] = {
_76
exports: {}
_76
};
_76
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
_76
return module.exports;
_76
}
_76
_76
!function () {
_76
__webpack_require__.d = function (exports, definition) {
_76
for (var key in definition) {
_76
if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
_76
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
_76
}
_76
}
_76
};
_76
}();
_76
_76
!function () {
_76
__webpack_require__.o = function (obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }
_76
}();
_76
_76
!function () {
_76
__webpack_require__.r = function (exports) {
_76
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
_76
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
_76
}
_76
Object.defineProperty(exports, '__esModule', { value: true });
_76
};
_76
}();
_76
_76
var __webpack_exports__ = {};
_76
_76
!function () {
_76
__webpack_require__.r(__webpack_exports__);
_76
var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/regular indirect named export/moduleA.js");
_76
console.log(({ namedA: (0, _moduleA__WEBPACK_IMPORTED_MODULE_0__.namedA)(), namedWithImportedValue: (0, _moduleA__WEBPACK_IMPORTED_MODULE_0__.namedWithImportedValue)() }));
_76
}();
_76
})()
_76
;

Of course, if moduleB would use a.namedWithImportedValue() in stead of a.namedA(), we would still end up in an infinite loop.

Takeaways

  1. Exporting only functions can lead to a max callsize exceeded error
  2. With named exports it's possible, but by no means guaranteed, to have a circular dependency without errors or other issues

Next

We will look at some variations on top of the examples so far to see if they make a difference.

We will visit:

Click here to go to the next page