48

I created a small JS module which I intend to make an npm package, but for now is just on GitHub. This module is written in ES6 and SCSS, and is thus relying on webpack and babel for transpilation.

To test it, I created a separate project with a similar setup (webpack and babel). After npm installing my module, when trying to import it into my index.js, I get the following error in Chrome Developer Tools: (with x being my module's name)

index.js:11 Uncaught TypeError: x__WEBPACK_IMPORTED_MODULE_1___default.a is not a constructor
    at eval (index.js:11)
    at Object../src/index.js (main.js:368)
    at __webpack_require__ (main.js:20)
    at eval (webpack:///multi_(:8081/webpack)-dev-server/client?:2:18)
    at Object.0 (main.js:390)
    at __webpack_require__ (main.js:20)
    at main.js:69
    at main.js:72

I've looked through countless answers and tried countless solutions, to no avail. My module's setup is as follows.

.babelrc

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["ie >= 11"]
      }
    }]
  ],
  "plugins": [
    "transform-es2015-modules-commonjs",
    "transform-class-properties"
  ]
}

webpack.common.js

const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const cleanWebpackPlugin = require('clean-webpack-plugin')

const baseSCSS = new ExtractTextPlugin('main/_base.css')
const themeSCSS = new ExtractTextPlugin('main/_theme.css')

module.exports = {
  entry: {
    example: [
      path.join(__dirname, 'src', 'example', 'index.js')
    ],
    main: [
      'idempotent-babel-polyfill',
      path.join(__dirname, 'src', 'index.js')
    ]
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: path.join('[name]', 'index.js')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        }
      },
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract(
          {
            fallback: 'style-loader',
            use: ['css-loader', 'sass-loader']
          }
        )
      },
      {
        test: /\_base-scss$/,
        use: baseSCSS.extract(
          {
            fallback: 'style-loader',
            use: ['css-loader', 'sass-loader']
          }
        )
      },
      {
        test: /\_theme-scss$/,
        use: themeSCSS.extract(
          {
            fallback: 'style-loader',
            use: ['css-loader', 'sass-loader']
          }
        )
      }
    ]
  },
  plugins: [
    new cleanWebpackPlugin('dist', {}),
    new ExtractTextPlugin({ filename: path.join('example', 'style.css') }),
    baseSCSS,
    themeSCSS,
    new HtmlWebpackPlugin({
      inject: false,
      hash: true,
      template: path.join(__dirname, 'src', 'example', 'index.html'),
      filename: path.join('example', 'index.html')
    })
  ]
}

webpack.prod.js

const merge = require('webpack-merge')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const webpack = require('webpack')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  plugins: [
    new UglifyJSPlugin({
      sourceMap: true
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ],
  mode: 'production'
})

package.json

{
  "name": "my-module-name",
  "version": "1.0.0-beta.1",
  "description": "",
  "main": "dist/main/index.js",
  "scripts": {
    "start": "webpack-dev-server --config webpack.dev.js",
    "server": "node src/server",
    "format": "prettier-standard 'src/**/*.js'",
    "lint": "eslint src",
    "build": "webpack --config webpack.prod.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Liran",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-eslint": "^8.2.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
    "babel-preset-env": "^1.7.0",
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^0.28.11",
    "eslint": "^4.19.1",
    "extract-text-webpack-plugin": "^4.0.0-beta.0",
    "html-webpack-plugin": "^3.2.0",
    "idempotent-babel-polyfill": "^0.1.1",
    "node-sass": "^4.9.0",
    "prettier-standard": "^8.0.1",
    "sass-loader": "^7.0.1",
    "style-loader": "^0.21.0",
    "uglifyjs-webpack-plugin": "^1.2.5",
    "webpack": "^4.6.0",
    "webpack-cli": "^2.0.15",
    "webpack-dev-middleware": "^3.1.3",
    "webpack-dev-server": "^3.1.3",
    "webpack-merge": "^4.1.2"
  }
}

Any help/pointers would be greatly appreciated. If you need more information, please let me know.

Liran H
  • 9,143
  • 7
  • 39
  • 52
  • 2
    what is the target set on your libraryTarget on your webpack config? – PlayMa256 May 20 '18 at 14:46
  • Hi @MatheusSilva, I don't have this property in my webpack config. I've added my whole setup to my post for greater clarity. – Liran H May 20 '18 at 15:48
  • 2
    Look at this!. https://webpack.js.org/guides/author-libraries/#expose-the-library – PlayMa256 May 20 '18 at 16:07
  • Wow @MatheusSilva, I can't believe it, but after adding the library and libraryTarget properties, my module now works flawlessly in my test project! Thank you so much for this really helpful pointer. If you'd like to write a short answer with the properties needed to be added (library and libraryTarget) and the source (the webpack link), I would gladly choose it as the accepted answer. – Liran H May 20 '18 at 16:30

8 Answers8

78

If you are not the library author and are having a problem consuming another library, you may be seeing an error like this:

TypeError: [LIBRARY_NAME]__WEBPACK_IMPORTED_MODULE_3__ is not a constructor

If that's the case, you may be importing the library incorrectly in your code (it may be a problem with default exports). Double check the library docs for usage.

It may be as simple as changing this:

import Foo from 'some-library/Foo';

to this:

import { Foo } from 'some-library';
David Calhoun
  • 8,315
  • 4
  • 30
  • 23
  • 1
    It worked like a charm when importing Typescript class in .js file. – Konrad Gałęzowski Apr 16 '20 at 14:06
  • 1
    Could you elaborate what is the difference when using the curly brackets and when not using them? Thanks! – brance May 18 '20 at 10:03
  • 13
    @brance Sure. The first example without curly braces is called the "default export" where the entire exported library is available through the `Foo` variable (e.g. `Foo.doSomething()`). The second example with curly braces is the "module exports" pattern, where modules are explicitly cherrypicked from the library piecemeal, which is ideal for treeshaking. The idea is that if a library has 100 modules and you only want to use 1, this pattern makes it explicit, so module bundlers know they don't need to bring in the other 99 modules, like they would have with the default export example. – David Calhoun May 18 '20 at 17:59
  • 2
    @DavidCalhoun Thank you very much for the explanation! – brance May 18 '20 at 19:56
26

It is not working because it is missing libraryTarget and library properties. By doing that webpack know which format of module you would like to create, i.e: commonjs (module.exports) or es (export).

I would do something like:

...
  output: {
    path: path.join(__dirname, 'dist'),
    filename: path.join('[name]', 'index.js'),
    library: "my-library",
    libraryTarget: "umd" // exposes and know when to use module.exports or exports.
  },
...
PlayMa256
  • 6,603
  • 2
  • 34
  • 54
5

Besides setting the libraryTarget, it may also be necessary to move the export in the JavaScript file to the default.

function MyClassName() {
  ...
}

export default MyClassName;

And then in the webpack configuration the library type umd ...

(Note that I have used the newer library.type instead the older libraryTarget (see https://webpack.js.org/configuration/output/#outputlibrarytarget).

 const path = require('path');
 
 module.exports = {
    mode: "production",
    entry: '../wherever/MyClassName.js',
    
    output: {
        library: {
          name: "MyClassName",
          type: "umd",  // see https://webpack.js.org/configuration/output/#outputlibrarytype
          export: "default",  // see https://github.com/webpack/webpack/issues/8480
        },
        filename: 'MyClassName.min.js',
        path: path.resolve(__dirname, '../wherever/target/')
    },
    optimization: {
        minimize: true
    }
 };

The export default makes the class available in JavaScript like the file was embedded directly, i.e.,

<script type="text/javascript" src="MyClassName.min.js"></script>
<script type="text/javascript">
<!--

var myInstance = new MyClassName();

// -->
</script>

Disclaimer: I added this answer even though the original question is three years old by now. After encountering the "is not a constructor" issue, it took me hours of searching to find the default solution. And that was the second time, I searched and found it :D

BurninLeo
  • 4,240
  • 4
  • 39
  • 56
  • Thanks a lot! This helped me to fix my issue regarding creating a React wrapper package around workbox-window using webpack. – n.sh Feb 18 '22 at 19:38
1

Cf. David Calhoun's answer, if you run into this with a third-party library, you may be trying to import a CommonJS module as an ECMAScript module. The workaround there seems to be to use require instead of import, e.g., instead of

import { Foo } from 'bar'

you need to write

const Foo = require('bar')

(There may be a more elegant way to handle this, but this is what worked for me.)

David Moles
  • 48,006
  • 27
  • 136
  • 235
1

For me, it was the cache issue. Just cleared the cookies, cache data and closed, reopened the browser. It worked.

Dinesh S
  • 139
  • 2
  • 13
1

In case something is using wepack 5 + babel 7

"webpack": "5.73.0",
"@babel/core": "7.4.4",
"@babel/preset-env": "7.4.4",
"babel-loader": "8.0.5",

AND want to use class instead function, this worked for me:

class Person {
   constructor(fname, lname, age, address) {
      this.fname = fname;
      this.lname = lname;
      this.age = age;
      this.address = address;
   }

   get fullname() {
      return this.fname +"-"+this.lname;
   }
}

export default Person;

In my case .babelrc was not necesary

JRichardsz
  • 14,356
  • 6
  • 59
  • 94
0

In my case, the error was being caused in React when trying to invoke JS's built-in Error constructor, or in other words, basically when calling throw new Error("something").

On inspection of my code, I realised I had a component called Error in my project which was being imported into the same file. The name clash between this component and JS's built-in Error constructor was causing the error mentioned in the question.

Nikhil Sinha
  • 481
  • 7
  • 5
0

tl;dr

Make sure that you import properly through index files.

Explanation

For me, this error was caused by importing through index files. I had multiple directories with their index.ts files that exported all the files inside the directory. These index files were accumulated/reexported by a main index.ts file so everything can be imported through it.

src/
├── index.ts
├── module1/
│   ├── index.ts
│   ├── file1.ts
│   └── file2.ts
└── module2/
    ├── index.ts
    ├── file3.ts
    └── file4.ts

In file4.ts I had an import like this:

import { file1Class, file2Class, file3Class } from "src";

I had to split it into two separate imports:

import { file1Class, file2Class } from "src/module1";
import { file3Class } from "src/module2";
totymedli
  • 29,531
  • 22
  • 131
  • 165