Errores inesperados de "Uncaught TypeError: XXX no es un constructor" con Babel y ES6


Estoy dando una oportunidad a Webpack, y estoy dando una oportunidad a las instrucciones en este tutorial, dar o tomar algunas cosas personalizadas.

Este es un código simple, realmente, pero estoy bastante desconcertado por este error, y siento que esto es algo tonto que me perdí.

Definí dos clases ES6, cada una correspondiente a una plantilla Handlebars, y se supone que el punto de entrada de mi aplicación reemplaza el marcador de posición HTML en el archivo de índice por su contenido:

Punto de entrada:

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');
}

Índice:

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

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

Botón:

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));
    }
}

Cabecera:

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})
        );
    }
}

Lamentablemente, no funciona, y obtengo estos dos errores al mostrar la página:

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

¿Qué me puedo perder?

Aquí está mi configuración de webpack:

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
    }
};
Author: Silver Quettier, 2016-04-03

5 answers

¿Qué podría faltar?

Babel asigna las exportaciones predeterminadas a la propiedad default. Por lo tanto, si utiliza require para importar módulos ES6, debe acceder a la propiedad default:

const Button = require('./Components/Button.js').default;
 45
Author: Felix Kling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-03 18:15:57

Me doy cuenta de que ya tienes una respuesta. Sin embargo, tuve un problema similar al que encontré una respuesta. Empezar mi propia pregunta y responderla parece raro. Así que voy a dejar esto aquí.

Tuve el mismo error que tú. Sin embargo, me las arreglé para resolverlo cambiando mi

export default {Class}

A

export default Class

No se por qué envolví la Clase en un objeto, pero recuerdo haberlo visto en algún lugar, así que empecé a usarlo.

Así que en lugar de devolver por defecto una Class devolvió un objeto como este {Class: Class}. Esto es completamente válido, pero romperá webpack + babel.

EDITAR: Desde entonces he llegado a saber por qué esto probablemente rompe babel+webpack. El export default está destinado a tener solo 1 exportación. Un objeto javascript puede contener muchas propiedades. Lo que significa que puede tener más de 1 exportación. (Véase: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export).

Para exportaciones múltiples utilice: export {definition1, definition2}.

Caso de uso: He utilizado esto en una situación en la que he creado una biblioteca que exporta diferentes tipos de un editor (mientras que el código subyacente era el mismo, la apariencia del editor cambia dependiendo de la exportación que utilice).

 8
Author: Byebye,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-16 09:04:31

Puedes poner export var __useDefault = true; justo después de exportar tu Clase.

export default class Header {
...
} 
export var __useDefault = true;
 6
Author: maufarinelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-16 04:36:52

No es el problema en esta pregunta en particular, pero por algunas razones, babel no ho clases en el mismo archivo.

Así que si declaras tu clase Token en la parte superior del archivo, y escribes más tarde new Token(), se ejecutará.

Si declaras tu clase después de la llamada al constructor, tendrás el error xxx is not a constructor

 1
Author: Nicolas Zozol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-17 06:10:18

Aunque esta no es la causa de su problema en particular, me encontré con un problema muy similar al intentar extraer babel de una aplicación de nodo existente que estaba utilizando la sintaxis de ES6 import y export, por lo que este post es para ayudar a cualquier otra persona que tenga problemas con esto en el futuro.

Babel resolverá cualquier dependencia circular entre un módulo y otro, por lo que puede usar import y export de ES6 con abandono imprudente. Sin embargo, si necesita deshacerse de babel y usar node nativo, necesitará sustituir cualquier import y exports por require. Esto puede reintroducir unas cuestiones de referencia circular latentes que babel estaba cuidando en el fondo. Si se encuentra en esta situación, busque un área en su código que se vea así:

Archivo A:

const B = require('B');

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

Archivo B:

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

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

Hay varias maneras diferentes de resolver este problema dependiendo de cómo estructuró su código. La forma más fácil es probablemente pasar B una referencia a A en lugar de crear un nuevo instancia de la clase A. También puede resolver dinámicamente la referencia al cargar A. Hay una miríada de otras alternativas, pero este es un buen lugar para comenzar.

 0
Author: xtro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-23 20:46:50