We've decided to use TypeScript in our application. We also want Babel to handle further transpiling that is required.

After evaluating our options, we choose to use ts-loader. It's perfect as we can set up our config to pass the output to Babel after ts-loader transpiles the TS. Exactly what we're looking for!

We start with installing TS and the loader.

yarn add -D ts-loader typescript

Next, we create our tsconfig.json file.

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

Finally, we add ts-loader to our webpack configuration.

module.exports = {
  ...,
  module: {
    rules: [
      {
      	test: /\.ts(x?)$/,
        exclude: /node_modules/,	          
        use: [
          {
            loader: 'babel-loader',
            options: { ... }
          },
          {
            loader: 'ts-loader'
          }
        ]
      },
    ]
  }
};

It's now time for us to test our new setup. To do this we should add a couple TypeScript files, let's have 2 files where one imports the other. Then we can spin up our dev server.

We should get a successful build (hopefully, works on my machine*). But, what happens when we go to the browser?

It turns out that we get a blank, white screen. What gives? My code is so simple. It's just a couple files. After checking the devtools, I see the following error:

Uncaught ReferenceError: exports is not defined
    at Module.eval (index.tsx:45)
    at eval (index.tsx:202)
    at Module.../../../../../../src/components/ApiWrapper/index.tsx (main.js:874)
    at __webpack_require__ (main.js:785)
    at fn (main.js:151)
    at Object.eval (index.tsx?aaeb:4)
    at eval (index.tsx:84)
    at Object.../../../../../../src/components/App/index.tsx (main.js:886)
    at __webpack_require__ (main.js:785)
    at fn (main.js:151)

My first thought is that there is a problem transpiling the ES Module Syntax (import / export). I thought most transpilers have this built in by default.

Turns out they do. Notice how it says exports not export. The former will be used in CommonJS modules, while the latter is for use in ES Module Syntax.

And this leads us to the problem. ts-loader outputs the transpiled code, which will later be passed, in memory, to Babel, in the CommonJS format. Hence, why we see the exports keyword.

This is caused by the configuration of compilerOptions.module, which we set to commonjs. We did this because it's the main module format for TS configs (mostly the default) and is all over the standard setup options on the TS docs site.

Babel requires an ES6+ (ESNext) file as an input. This means that it won't be able to "import" the CommonJS output that our ts-loader is currently producing. To fix this, we just need to change one option in our tsconfig.json file.

{
  "compilerOptions": {
-   "module": "commonjs", 
+   "module": "es6",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

You could also use esnext or es2020. Basically, anything that isn't commonjs or es5.

After changing this compiler option and restarting your dev server, we should not see the error anymore!

Until next time...

* just a joke