npmでインストールしたmoduleのクラスをextendしたら TypeError: Class constructor classname cannot be invoked without 'new'

表題のエラーに遭遇したので、その原因と対処法を記録しておきます。

## 環境

この記事は以下の環境を前提にしています。

*▼package.json*

```json
"devDependencies"{
  "@babel/cli": "^7.10.5",
  "@babel/core": "^7.10.5",
  "@babel/preset-env": "^7.10.4",
  "typescript": "^3.8.3"
}
```

記事を読む前に、お手元の環境をご確認ください。

## 問題発生の手順

1. npmでES6ネイティブのモジュールをインストールする。
2. モジュール内のクラスを継承する。
3. BabelなどでES5にトランスパイルする。


```js
import { ES6Class } from "module";
export class ClassName extends ES6Class {...
```

上記コードの例の場合、npmからES6ネイティブの`module`という名前のパッケージをインストールし、
`ES6Class`というクラスをインポート、`ClassName`というクラスに継承しています。

## 症状

上記の手順を実行し、生成されたjavascriptを実行すると、以下のエラーが発生します。

```console
Uncaught TypeError: Class constructor ClassName cannot be invoked without 'new'
    at new ClassName (ClassName.js:35)
    at onDomContentsLoaded (main.js:19)
    at eval (main.js:32)
    at Module../src/main.js (bundle.js:133)
    at __webpack_require__ (bundle.js:20)
    at bundle.js:84
    at bundle.js:87
classname	@	classname.js:35
onDomContentsLoaded	@	main.js:19
(anonymous)	@	main.js:32
./src/main.js	@	bundle.js:133
__webpack_require__	@	bundle.js:20
(anonymous)	@	bundle.js:84
(anonymous)	@	bundle.js:87
```

`ClassName`クラスのコンストラクターの実行に失敗します。

## 原因

この問題は、ネイティブのES6クラスを継承し、さらにES5に変換することによって発生します。この変換に失敗するため、上記の症状が発生します。

## 対処法

あなたが問題の発生するパッケージの利用者か、開発者かによって対処法が分かれます。

### あなたがパッケージの利用者の場合

変換の対象をES5ではなくES6に指定することで、この問題は解決します。

#### Babelの場合

Babelでトランスパイルを使用している場合、`@babel/preset-env`で変換対象を指定します。
`node`を変換対象に指定することでES6のjsファイルが出力されます。

*▼.babelrc*

```json
{
  "presets": [
    [ "@babel/preset-env", {
      "targets": { "node": true }
    }]
  ]
}
```

#### TypeScriptの場合

TypeScriptの場合、コンパイラオブションで変換対象の指定ができます。

*▼tsconfig.json*

```json
{
  "compilerOptions": {
    "target": "es6",
  }
}
```

#### 副作用

変換先のjsファイルはES6の構文を使用していますので、IE11では動作しません。

[ECMAScript 6 compatibility table](https://kangax.github.io/compat-table/es6/)

IE11をターゲットとする場合は、extendsを使用しないなど他の回避方法を検討してください。

### あなたがパッケージの開発者の場合

問題が発生するパッケージを、`CommonJS`と`ESModules`の両対応にすることで問題が解決します。npmモジュールに両方のスクリプトがあり、その状態がpackage.jsonに記述されていれば、webpackなどのモジュールバンドラーがファイルを適切に取りあつかってくれます。

#### TypeScriptの場合

まずはCommonJSを出力する`tsconfig.json`を用意します。この設定では、仮に`lib`というディレクトリにCommonJSと型定義ファイルが出力されます。

*▼tsconfig.json*

```json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "declaration": true,
    "declarationMap": true,
    "outDir": "./lib"
  }
}
```

`tsconfig.json`を[継承](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#configuration-inheritance-with-extends)する`tsconfig.esm.json`を用意します。`esm`というディレクトリに、ESModulesが出力されます。こちらでは型定義ファイルは出力しません。

*▼tsconfig.esm.json*

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "target": "es6",
    "module": "es2015",
    "outDir": "./esm",
    "declaration": false,
    "declarationMap": false
  }
}

```

最後に、package.jsonにこの2つのファイルの場所を記述します。

*▼package.json*

```json
{
  "main": "./lib/index.js",
  "types": "./lib/index.d.ts",
  "module": "./esm/index.js",
}
```

package.jsonに`module`フィールドを追加し、ESModules形式で出力されたファイルを指定します。モジュールバンドラーはこのフィールドを参照して、必要なファイルを取り出します。

## 参考記事
[stackoverflow - Javascript ES6 TypeError: Class constructor Client cannot be invoked without 'new'](https://stackoverflow.com/questions/51860043/javascript-es6-typeerror-class-constructor-client-cannot-be-invoked-without-ne/51860850)