Unexpected “Uncaught TypeError: XXX is not a const

2019-02-05 14:27发布

I am giving a try to Webpack, and am giving a try to the instructions in this tutorial, give or take a few custom things.

This is simple code, really, but I'm quite puzzled about this error, and feel this is something silly that I missed.

I defined two ES6 classes, each corresponding to a Handlebars template, and my app's entrypoint is supposed to replace the placeholder HTML in the index file by their contents:

Entrypoint:

import './bloj.less'

// If we have a link, render the Button component on it
if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button.js');
        const button = new Button('9gag.com');

        button.render('a');
    }, 'button');
}

// If we have a title, render the Header component on it
if (document.querySelectorAll('h1').length) {
    require.ensure([], () => {
        const Header = require('./Components/Header.js');

        new Header().render('h1');
    }, 'header');
}

Index:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <h1>My title</h1>
    <a>Click me</a>

    <script src="build/bloj.js"></script>
</body>
</html>

Button:

import $ from 'jquery';
import './Button.less';

export default class Button {

    constructor(link) {
        this.link = link;
    }

    onClick(event) {
        event.preventDefault();
        alert(this.link);
    }

    render(node) {
        const text = $(node).text();
        var compiled = require('./Button.hbs');

        // Render our button
        $(node).html(
            compiled({"text": text, "link": this.link})
        );

        // Attach our listeners
        $('.button').click(this.onClick.bind(this));
    }
}

Header:

import $ from 'jquery';
import './Header.less';

export default class Header {
    render(node) {
        const text = $(node).text();
        var compiled = require('./Header.hbs');

        // Render the header
        $(node).html(
            compiled({"text": text})
        );
    }
}

Sadly, it does not work, and I get both these errors when displaying the page:

Uncaught TypeError: Header is not a constructor
Uncaught TypeError: Button is not a constructor

What could I be missing?

Here is my webpack configuration:

var path = require('path');
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractPlugin = require('extract-text-webpack-plugin');

var production = process.env.NODE_ENV === 'production';
var appName = 'bloj';
var entryPoint = './src/bloj.js';
var outputDir =  './build/';
var publicDir = './build/';

// ************************************************************************** //

var plugins = [
    //new ExtractPlugin(appName + '.css', {allChunks: true}),
    new CleanPlugin(outputDir),
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main',
        children:  true,
        minChunks: 2
    })
];

if (production) {
    plugins = plugins.concat([
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.OccurenceOrderPlugin(),
        new webpack.optimize.MinChunkSizePlugin({
            minChunkSize: 51200 // 50ko
        }),
        new webpack.optimize.UglifyJsPlugin({
            mangle:   true,
            compress: {
                warnings: false // Suppress uglification warnings
            }
        }),
        new webpack.DefinePlugin({
            __SERVER__:      false,
            __DEVELOPMENT__: false,
            __DEVTOOLS__:    false,
            'process.env':   {
                BABEL_ENV: JSON.stringify(process.env.NODE_ENV)
            }
        })
    ]);
}

module.exports = {
    entry:  entryPoint,
    output: {
        path:     outputDir,
        filename: appName + '.js',
        chunkFilename: '[name].js',
        publicPath: publicDir
    },
    debug:   !production,
    devtool: production ? false : 'eval',
    module: {
        loaders: [
            {
                test: /\.js/,
                loader: "babel",
                include: path.resolve(__dirname, 'src'),
                query: {
                    presets: ['es2015']
                }
            },
            {
                test: /\.less/,
                //loader: ExtractPlugin.extract('style', 'css!less')
                loader: "style!css!less"
            },
            {
                test:   /\.html/,
                loader: 'html'
            },
            {
                test: /\.hbs/,
                loader: "handlebars-template-loader"
            }
        ]
    },
    plugins: plugins,
    node: {
        fs: "empty" // Avoids Handlebars error messages
    }
};

5条回答
Anthone
2楼-- · 2019-02-05 14:58

You can just put export var __useDefault = true; just after exporting your Class.

export default class Header {
...
} 
export var __useDefault = true;
查看更多
Rolldiameter
3楼-- · 2019-02-05 15:04

It's not the problem in this particular question, but for some reasons, babel does not hoist classes in the same file.

So if you declare your class Token at the top of the file, and write later new Token(), it will run.

If you declare your class after the constructor call, you will have the xxx is not a constructor error

查看更多
放我归山
4楼-- · 2019-02-05 15:05

I realize that you already have an answer. However I had a similar issue to which I found an answer. Starting my own question and answering it seems weird. So I'm just going to leave this here.

I had the same error as you got. However, I managed to solve it by changing my

export default {Class}

to

export default Class

I don't know why I wrapped the Class in an object but I remember having seen it somewhere so I just started using it.

So instead of the default returning a Class it returned an object like this {Class: Class}. This is completely valid yet it will break webpack+babel.

EDIT: I've since come to know why this probably breaks babel+webpack. The export default is meant to only have 1 export. A javascript-object can contain many properties. Which means it can have more than 1 export. (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export).

For multiple exports use: export {definition1, definition2}.

Use-case: I've used this in a situation where I've created a library which exported different types of an editor (while the underlying code was the same, the appearance of the editor changes depending on which export you use).

查看更多
Bombasti
5楼-- · 2019-02-05 15:07

Although this is not the cause of your particular issue, I ran into a very similar problem when trying to rip babel out of an existing node app that was using ES6's import and export syntax, so this post is to help out anyone else struggling with this in the future.

Babel will resolve any circular dependencies between one module and another, so you can use ES6's import and export with reckless abandon. However, if you need to get rid of babel and use native node, you will need to replace any import and exports with require. This can reintroduce a latent circular reference issues that babel was taking care of in the background. If you find yourself in this situation, look for an area in your code that looks like this:

File A:

const B = require('B');

class A {
  constructor() {
    this.b = new B();
  }
}
module.exports = A;

File B:

const A = require('A'); // this line causes the error

class B {
  constructor() {
    this.a = new A();
  }
}
module.exports = B;

There are several different ways to resolve this issue depending on how you structured your code. The easiest way is probably to pass B a reference to A instead of creating a new instance of class A. You could also dynamically resolve the reference when loading A. There are a myriad of other alternatives, but this is a good place to get started.

查看更多
做个烂人
6楼-- · 2019-02-05 15:11

What could I be missing?

Babel assigns default exports to the default property. So if you use require to import ES6 modules, you need to access the default property:

const Button = require('./Components/Button.js').default;
查看更多
登录 后发表回答