LoginSignup
28
18

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-03-25

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

環境

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

▼package.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にトランスパイルする。
import { ES6Class } from "module";
export class ClassName extends ES6Class {...

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

症状

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

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

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

TypeScriptの場合

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

▼tsconfig.json

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

副作用

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

ECMAScript 6 compatibility table

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

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

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

TypeScriptの場合

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

▼tsconfig.json

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

tsconfig.json継承するtsconfig.esm.jsonを用意します。esmというディレクトリに、ESModulesが出力されます。こちらでは型定義ファイルは出力しません。

▼tsconfig.esm.json

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

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

▼package.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'

28
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
18