Recently I faced a very odd problem while writing tests in TypeScript. While running the tests, an error was thrown that the module gray-matter (Github), which is a front matter parser. But when I build the code, the emitted JavaScript works perfectly.
This post is all about tracing the error of that import.
TLDR; Click here to read the TypeScript documentation on
esModuleInteropoption. Enabling this options also enablesallowSyntheticDefaultImportsoption. Click here to read about it.
The following are two of multiple ways to import from a module in TypeScript -
- Importing all exports of a module
 
import * as someModule from "some-module";
// Which transpile to the following
// const someModule = require("some-module");
- Importing the default export
 
import someModule from "some-module";
// Which transpile to the following
// const someModule = require("some-module").default;
The difference between those two imports -
import * as someModule from "some-module"; | import someModule from "some-module"; | 
|---|---|
| Turns into a namespace import | Turns into a default import | 
| Can only be an object (ES6 module spec) | Can be function, object, or others | 
By enabling esModuleInterop options, change the behavior of the compiler and adds helper functions that provide a shim to ensure compatibility in the emitted JavaScript. You can go to the TypeScript documentation to view detailed examples with emitted code.
The Problem
The Node.js package eco-system is huge. Also, there are multiple module formatting system, including but not limited to -
- CommonJS (used by Node.js)
 - ES6
 - AMD
 - UMD etc.
 
Unlike ES6, most module formatting systems didn’t adhere to a specific spec. Thus when using a package that is older or uses another module formatting that doesn’t use the ES6 spec, will throw errors. In my case, I found it out with the package gray-matter.
First, I didn’t enable esModuleInterop. So, I used the import below -
import * as matter from "gray-matter";
which results in the following error when I was running test in Jest (Github)
TypeError: matter is not a function
For reference, the gray-matter exports in the following way -
/**
 * Expose `matter`
 */
matter.cache = {};
matter.clearCache = function () {
  matter.cache = {};
};
module.exports = matter;
So, let’s go down the rabbit hole with Jest. This is the Babel (Github) configuration to support test file in TypeScript
// babel.config.js
module.exports = {
  presets: [
    ["@babel/preset-env", { targets: { node: "current" } }],
    "@babel/preset-typescript",
  ],
};
Bable uses @babel/preset-typescript package to configure TypeScript support. Now, @babel/preset-typescript uses another package named @babel/plugin-transform-typescript to transpile from TypeScript to JavaScript.
In the description the package @babel/plugin-transform-typescript (documentation), it says the following
--esModuleInteropThis is the default behavior of Babel when transpiling ECMAScript modules.
That means, the import that I was using handled differently while running the test. That is because the test code and the compiled code are emitted using different module resolution configurations. Thus import * as matter from "gray-matter"; will transpile differently with the esModuleInterop enabled in the test code, and not enable in the compilation.
The Solution
It’s quite easy, enable the esModuleInterop options in tsconfig.json
{
  "compilerOptions": {
    "esModuleInterop": true
  }
}
And convert namespace imports to default imports -
import matter from "gray-matter";
The Lesson
Every tool comes with a default configuration. It’s crucial to keep track of the configuration that they use and always keep them in sync.
Reference Links
| Name | Link | 
|---|---|
| TypeScript Compiler Option: esModuleInterop | Documentation | 
| TypeScript Compiler Option: allowSyntheticDefaultImports | Documentation | 
| gray-matter | Github | 
| Front Matter | Wikipedia | 
| Jest | Github | 
| Babel | Github | 
| @babel/preset-typescript | Documentation | 
| @babel/plugin-transform-typescript | Documentation |